[Lldb-commits] [lldb] 9ea6dd5 - Add a corefile style option to process save-core; skinny corefiles

Jason Molenda via lldb-commits lldb-commits at lists.llvm.org
Sun Jun 20 12:27:04 PDT 2021


Author: Jason Molenda
Date: 2021-06-20T12:26:54-07:00
New Revision: 9ea6dd5cfac0b233fbb148c1e2d0f81f816737c8

URL: https://github.com/llvm/llvm-project/commit/9ea6dd5cfac0b233fbb148c1e2d0f81f816737c8
DIFF: https://github.com/llvm/llvm-project/commit/9ea6dd5cfac0b233fbb148c1e2d0f81f816737c8.diff

LOG: Add a corefile style option to process save-core; skinny corefiles

Add a new feature to process save-core on Darwin systems -- for
lldb to create a user process corefile with only the dirty (modified
memory) pages included.  All of the binaries that were used in the
corefile are assumed to still exist on the system for the duration
of the use of the corefile.  A new --style option to process save-core
is added, so a full corefile can be requested if portability across
systems, or across time, is needed for this corefile.

debugserver can now identify the dirty pages in a memory region
when queried with qMemoryRegionInfo, and the size of vm pages is
given in qHostInfo.

Create a new "all image infos" LC_NOTE for Mach-O which allows us
to describe all of the binaries that were loaded in the process --
load address, UUID, file path, segment load addresses, and optionally
whether code from the binary was executing on any thread.  The old
"read dyld_all_image_infos and then the in-memory Mach-O load
commands to get segment load addresses" no longer works when we
only have dirty memory.

rdar://69670807
Differential Revision: https://reviews.llvm.org/D88387

Added: 
    lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
    lldb/test/API/macosx/skinny-corefile/Makefile
    lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
    lldb/test/API/macosx/skinny-corefile/main.c
    lldb/test/API/macosx/skinny-corefile/present.c
    lldb/test/API/macosx/skinny-corefile/present.h
    lldb/test/API/macosx/skinny-corefile/to-be-removed.c
    lldb/test/API/macosx/skinny-corefile/to-be-removed.h

Modified: 
    lldb/bindings/interface/SBMemoryRegionInfo.i
    lldb/docs/lldb-gdb-remote.txt
    lldb/include/lldb/API/SBMemoryRegionInfo.h
    lldb/include/lldb/Core/PluginManager.h
    lldb/include/lldb/Symbol/ObjectFile.h
    lldb/include/lldb/Target/MemoryRegionInfo.h
    lldb/include/lldb/lldb-enumerations.h
    lldb/include/lldb/lldb-private-interfaces.h
    lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py
    lldb/source/API/SBMemoryRegionInfo.cpp
    lldb/source/API/SBProcess.cpp
    lldb/source/Commands/CommandObjectMemory.cpp
    lldb/source/Commands/CommandObjectProcess.cpp
    lldb/source/Commands/Options.td
    lldb/source/Core/PluginManager.cpp
    lldb/source/Interpreter/CommandObject.cpp
    lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
    lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
    lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
    lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
    lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
    lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
    lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py
    lldb/tools/debugserver/source/DNBDefs.h
    lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
    lldb/tools/debugserver/source/RNBRemote.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/bindings/interface/SBMemoryRegionInfo.i b/lldb/bindings/interface/SBMemoryRegionInfo.i
index 6a2ad6a3e3649..3460dc0d06e22 100644
--- a/lldb/bindings/interface/SBMemoryRegionInfo.i
+++ b/lldb/bindings/interface/SBMemoryRegionInfo.i
@@ -46,6 +46,42 @@ public:
     const char *
     GetName ();
 
+    %feature("autodoc", "
+        GetRegionEnd(SBMemoryRegionInfo self) -> lldb::addr_t
+        Returns whether this memory region has a list of modified (dirty)
+        pages available or not.  When calling GetNumDirtyPages(), you will
+        have 0 returned for both \"dirty page list is not known\" and 
+        \"empty dirty page list\" (that is, no modified pages in this
+        memory region).  You must use this method to disambiguate.") HasDirtyMemoryPageList;
+    bool 
+    HasDirtyMemoryPageList();
+
+    %feature("autodoc", "
+        GetNumDirtyPages(SBMemoryRegionInfo self) -> uint32_t
+        Return the number of dirty (modified) memory pages in this
+        memory region, if available.  You must use the 
+        SBMemoryRegionInfo::HasDirtyMemoryPageList() method to
+        determine if a dirty memory list is available; it will depend
+        on the target system can provide this information.") GetNumDirtyPages;
+    uint32_t 
+    GetNumDirtyPages();
+
+    %feature("autodoc", "
+        GetDirtyPageAddressAtIndex(SBMemoryRegionInfo self, uint32_t idx) -> lldb::addr_t
+        Return the address of a modified, or dirty, page of memory.
+        If the provided index is out of range, or this memory region 
+        does not have dirty page information, LLDB_INVALID_ADDRESS 
+        is returned.") GetDirtyPageAddressAtIndex;
+    addr_t 
+    GetDirtyPageAddressAtIndex(uint32_t idx);
+
+    %feature("autodoc", "
+        GetPageSize(SBMemoryRegionInfo self) -> int
+        Return the size of pages in this memory region.  0 will be returned
+        if this information was unavailable.") GetPageSize();
+    int
+    GetPageSize();
+
     bool
     operator == (const lldb::SBMemoryRegionInfo &rhs) const;
 

diff  --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt
index 9d6b41ad086e2..d4ec112f3936c 100644
--- a/lldb/docs/lldb-gdb-remote.txt
+++ b/lldb/docs/lldb-gdb-remote.txt
@@ -869,10 +869,14 @@ distribution_id: optional. For linux, specifies distribution id (e.g. ubuntu, fe
 osmajor: optional, specifies the major version number of the OS (e.g. for macOS 10.12.2, it would be 10)
 osminor: optional, specifies the minor version number of the OS (e.g. for macOS 10.12.2, it would be 12)
 ospatch: optional, specifies the patch level number of the OS (e.g. for macOS 10.12.2, it would be 2)
+vm-page-size: optional, specifies the target system VM page size, base 10.
+           Needed for the "dirty-pages:" list in the qMemoryRegionInfo
+           packet, where a list of dirty pages is sent from the remote 
+           stub.  This page size tells lldb how large each dirty page is.
 addressing_bits: optional, specifies how many bits in addresses are
 		 significant for addressing, base 10.  If bits 38..0
 		 in a 64-bit pointer are significant for addressing,
-		 then the value is 39.  This is needed on e.g. Aarch64
+		 then the value is 39.  This is needed on e.g. AArch64
 		 v8.3 ABIs that use pointer authentication, so lldb
 		 knows which bits to clear/set to get the actual
 		 addresses.
@@ -1174,6 +1178,18 @@ tuples to return are:
                                      // a hex encoded string value that
                                      // contains an error string
 
+    dirty-pages:[<hexaddr>][,<hexaddr]; // A list of memory pages within this
+                 // region that are "dirty" -- they have been modified.
+                 // Page addresses are in base16.  The size of a page can
+                 // be found from the qHostInfo's page-size key-value.
+                 //
+                 // If the stub supports identifying dirty pages within a
+                 // memory region, this key should always be present for all
+                 // qMemoryRegionInfo replies.  This key with no pages 
+                 // listed ("dirty-pages:;") indicates no dirty pages in 
+                 // this memory region.  The *absence* of this key means
+                 // that this stub cannot determine dirty pages.
+
 If the address requested is not in a mapped region (e.g. we've jumped through
 a NULL pointer and are at 0x0) currently lldb expects to get back the size
 of the unmapped region -- that is, the distance to the next valid region.

diff  --git a/lldb/include/lldb/API/SBMemoryRegionInfo.h b/lldb/include/lldb/API/SBMemoryRegionInfo.h
index d82c706065597..122226b9a0c5c 100644
--- a/lldb/include/lldb/API/SBMemoryRegionInfo.h
+++ b/lldb/include/lldb/API/SBMemoryRegionInfo.h
@@ -73,6 +73,40 @@ class LLDB_API SBMemoryRegionInfo {
   ///     region. If no name can be determined the returns nullptr.
   const char *GetName();
 
+  /// Returns whether this memory region has a list of memory pages
+  /// that have been modified -- that are dirty.
+  ///
+  /// \return
+  ///     True if the dirty page list is available.
+  bool HasDirtyMemoryPageList();
+
+  /// Returns the number of modified pages -- dirty pages -- in this
+  /// memory region.
+  ///
+  /// \return
+  ///     The number of dirty page entries will be returned.  If
+  ///     there are no dirty pages in this memory region, 0 will
+  ///     be returned.  0 will also be returned if the dirty page
+  ///     list is not available for this memory region -- you must
+  ///     use HasDirtyMemoryPageList() to check for that.
+  uint32_t GetNumDirtyPages();
+
+  /// Returns the address of a memory page that has been modified in
+  /// this region.
+  ///
+  /// \return
+  ///     Returns the address for his dirty page in the list.
+  ///     If this memory region does not have a dirty page list,
+  ///     LLDB_INVALID_ADDRESS is returned.
+  addr_t GetDirtyPageAddressAtIndex(uint32_t idx);
+
+  /// Returns the size of a memory page in this region.
+  ///
+  /// \return
+  ///     Returns the size of the memory pages in this region,
+  ///     or 0 if this information is unavailable.
+  int GetPageSize();
+
   bool operator==(const lldb::SBMemoryRegionInfo &rhs) const;
 
   bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const;

diff  --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 7ddd932da33f1..42fa953797c5f 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -191,7 +191,8 @@ class PluginManager {
   GetObjectFileCreateMemoryCallbackForPluginName(ConstString name);
 
   static Status SaveCore(const lldb::ProcessSP &process_sp,
-                         const FileSpec &outfile);
+                         const FileSpec &outfile,
+                         lldb::SaveCoreStyle &core_style);
 
   // ObjectContainer
   static bool

diff  --git a/lldb/include/lldb/Symbol/ObjectFile.h b/lldb/include/lldb/Symbol/ObjectFile.h
index 080724cb86bdd..1e29cf53b78b3 100644
--- a/lldb/include/lldb/Symbol/ObjectFile.h
+++ b/lldb/include/lldb/Symbol/ObjectFile.h
@@ -666,6 +666,22 @@ class ObjectFile : public std::enable_shared_from_this<ObjectFile>,
   /// Creates a plugin-specific call frame info
   virtual std::unique_ptr<CallFrameInfo> CreateCallFrameInfo();
 
+  /// Load binaries listed in a corefile
+  ///
+  /// A corefile may have metadata listing binaries that can be loaded,
+  /// and the offsets at which they were loaded.  This method will try
+  /// to add them to the Target.  If any binaries were loaded,
+  ///
+  /// \param[in] process
+  ///     Process where to load binaries.
+  ///
+  /// \return
+  ///     Returns true if any binaries were loaded.
+
+  virtual bool LoadCoreFileImages(lldb_private::Process &process) {
+    return false;
+  }
+
 protected:
   // Member variables.
   FileSpec m_file;

diff  --git a/lldb/include/lldb/Target/MemoryRegionInfo.h b/lldb/include/lldb/Target/MemoryRegionInfo.h
index 19c6c17ef9012..c43f27e0c366b 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -10,8 +10,11 @@
 #ifndef LLDB_TARGET_MEMORYREGIONINFO_H
 #define LLDB_TARGET_MEMORYREGIONINFO_H
 
+#include <vector>
+
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/RangeMap.h"
+#include "llvm/ADT/Optional.h"
 #include "llvm/Support/FormatProviders.h"
 
 namespace lldb_private {
@@ -32,10 +35,7 @@ class MemoryRegionInfo {
 
   RangeType &GetRange() { return m_range; }
 
-  void Clear() {
-    m_range.Clear();
-    m_read = m_write = m_execute = m_memory_tagged = eDontKnow;
-  }
+  void Clear() { *this = MemoryRegionInfo(); }
 
   const RangeType &GetRange() const { return m_range; }
 
@@ -97,11 +97,33 @@ class MemoryRegionInfo {
            m_write == rhs.m_write && m_execute == rhs.m_execute &&
            m_mapped == rhs.m_mapped && m_name == rhs.m_name &&
            m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize &&
-           m_memory_tagged == rhs.m_memory_tagged;
+           m_memory_tagged == rhs.m_memory_tagged &&
+           m_pagesize == rhs.m_pagesize;
   }
 
   bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); }
 
+  /// Get the target system's VM page size in bytes.
+  /// \return
+  ///     0 is returned if this information is unavailable.
+  int GetPageSize() { return m_pagesize; }
+
+  /// Get a vector of target VM pages that are dirty -- that have been
+  /// modified -- within this memory region.  This is an Optional return
+  /// value; it will only be available if the remote stub was able to
+  /// detail this.
+  llvm::Optional<std::vector<lldb::addr_t>> &GetDirtyPageList() {
+    return m_dirty_pages;
+  }
+
+  void SetPageSize(int pagesize) { m_pagesize = pagesize; }
+
+  void SetDirtyPageList(std::vector<lldb::addr_t> pagelist) {
+    if (m_dirty_pages.hasValue())
+      m_dirty_pages.getValue().clear();
+    m_dirty_pages = std::move(pagelist);
+  }
+
 protected:
   RangeType m_range;
   OptionalBool m_read = eDontKnow;
@@ -112,6 +134,8 @@ class MemoryRegionInfo {
   OptionalBool m_flash = eDontKnow;
   lldb::offset_t m_blocksize = 0;
   OptionalBool m_memory_tagged = eDontKnow;
+  int m_pagesize = 0;
+  llvm::Optional<std::vector<lldb::addr_t>> m_dirty_pages;
 };
   
 inline bool operator<(const MemoryRegionInfo &lhs,

diff  --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index db6289cfb90dc..0e22ad517802e 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -601,6 +601,7 @@ enum CommandArgumentType {
   eArgTypeCommand,
   eArgTypeColumnNum,
   eArgTypeModuleUUID,
+  eArgTypeSaveCoreStyle,
   eArgTypeLastArg // Always keep this entry as the last entry in this
                   // enumeration!!
 };
@@ -1111,6 +1112,14 @@ enum CommandInterpreterResult {
   /// Stopped because quit was requested.
   eCommandInterpreterResultQuitRequested,
 };
+
+// Style of core file to create when calling SaveCore.
+enum SaveCoreStyle {
+  eSaveCoreUnspecified = 0,
+  eSaveCoreFull = 1,
+  eSaveCoreDirtyOnly = 2,
+};
+
 } // namespace lldb
 
 #endif // LLDB_LLDB_ENUMERATIONS_H

diff  --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h
index 3086a01942666..cdc4dc0dee42a 100644
--- a/lldb/include/lldb/lldb-private-interfaces.h
+++ b/lldb/include/lldb/lldb-private-interfaces.h
@@ -54,7 +54,9 @@ typedef ObjectFile *(*ObjectFileCreateMemoryInstance)(
     const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp,
     const lldb::ProcessSP &process_sp, lldb::addr_t offset);
 typedef bool (*ObjectFileSaveCore)(const lldb::ProcessSP &process_sp,
-                                   const FileSpec &outfile, Status &error);
+                                   const FileSpec &outfile,
+                                   lldb::SaveCoreStyle &core_style,
+                                   Status &error);
 typedef EmulateInstruction *(*EmulateInstructionCreateInstance)(
     const ArchSpec &arch, InstructionType inst_type);
 typedef OperatingSystem *(*OperatingSystemCreateInstance)(Process *process,

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py
index 9c70a5558e7d7..67bffdd5931a8 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py
@@ -723,7 +723,8 @@ def parse_memory_region_packet(self, context):
                  "permissions",
                  "flags",
                  "name",
-                 "error"])
+                 "error",
+                 "dirty-pages"])
             self.assertIsNotNone(val)
 
         mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", ""))

diff  --git a/lldb/source/API/SBMemoryRegionInfo.cpp b/lldb/source/API/SBMemoryRegionInfo.cpp
index 2a28b99c72d73..ab74d559387fb 100644
--- a/lldb/source/API/SBMemoryRegionInfo.cpp
+++ b/lldb/source/API/SBMemoryRegionInfo.cpp
@@ -116,6 +116,42 @@ const char *SBMemoryRegionInfo::GetName() {
   return m_opaque_up->GetName().AsCString();
 }
 
+bool SBMemoryRegionInfo::HasDirtyMemoryPageList() {
+  LLDB_RECORD_METHOD_NO_ARGS(bool, SBMemoryRegionInfo, HasDirtyMemoryPageList);
+
+  return m_opaque_up->GetDirtyPageList().hasValue();
+}
+
+uint32_t SBMemoryRegionInfo::GetNumDirtyPages() {
+  LLDB_RECORD_METHOD_NO_ARGS(uint32_t, SBMemoryRegionInfo, GetNumDirtyPages);
+
+  uint32_t num_dirty_pages = 0;
+  llvm::Optional<std::vector<addr_t>> dirty_page_list =
+      m_opaque_up->GetDirtyPageList();
+  if (dirty_page_list.hasValue())
+    num_dirty_pages = dirty_page_list.getValue().size();
+
+  return num_dirty_pages;
+}
+
+addr_t SBMemoryRegionInfo::GetDirtyPageAddressAtIndex(uint32_t idx) {
+  LLDB_RECORD_METHOD(addr_t, SBMemoryRegionInfo, GetDirtyPageAddressAtIndex,
+                     (uint32_t), idx);
+
+  addr_t dirty_page_addr = LLDB_INVALID_ADDRESS;
+  const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
+      m_opaque_up->GetDirtyPageList();
+  if (dirty_page_list.hasValue() && idx < dirty_page_list.getValue().size())
+    dirty_page_addr = dirty_page_list.getValue()[idx];
+
+  return dirty_page_addr;
+}
+
+int SBMemoryRegionInfo::GetPageSize() {
+  LLDB_RECORD_METHOD_NO_ARGS(int, SBMemoryRegionInfo, GetPageSize);
+  return m_opaque_up->GetPageSize();
+}
+
 bool SBMemoryRegionInfo::GetDescription(SBStream &description) {
   LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetDescription,
                      (lldb::SBStream &), description);

diff  --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 58ed3d76a0bd2..f0c7c5f75a2e5 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -1227,7 +1227,8 @@ lldb::SBError SBProcess::SaveCore(const char *file_name) {
   }
 
   FileSpec core_file(file_name);
-  error.ref() = PluginManager::SaveCore(process_sp, core_file);
+  SaveCoreStyle core_style = SaveCoreStyle::eSaveCoreFull;
+  error.ref() = PluginManager::SaveCore(process_sp, core_file, core_style);
   return LLDB_RECORD_RESULT(error);
 }
 

diff  --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 5c1bbc2f919eb..addbf5878a56b 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1678,6 +1678,28 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
       if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes)
         result.AppendMessage("memory tagging: enabled");
 
+      const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
+          range_info.GetDirtyPageList();
+      if (dirty_page_list.hasValue()) {
+        const size_t page_count = dirty_page_list.getValue().size();
+        result.AppendMessageWithFormat(
+            "Modified memory (dirty) page list provided, %zu entries.\n",
+            page_count);
+        if (page_count > 0) {
+          bool print_comma = false;
+          result.AppendMessageWithFormat("Dirty pages: ");
+          for (size_t i = 0; i < page_count; i++) {
+            if (print_comma)
+              result.AppendMessageWithFormat(", ");
+            else
+              print_comma = true;
+            result.AppendMessageWithFormat("0x%" PRIx64,
+                                           dirty_page_list.getValue()[i]);
+          }
+          result.AppendMessageWithFormat(".\n");
+        }
+      }
+
       m_prev_end_addr = range_info.GetRange().GetRangeEnd();
       result.SetStatus(eReturnStatusSuccessFinishResult);
       return true;

diff  --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp
index ad608a6010be0..a0c5fc9bbf957 100644
--- a/lldb/source/Commands/CommandObjectProcess.cpp
+++ b/lldb/source/Commands/CommandObjectProcess.cpp
@@ -1161,26 +1161,91 @@ class CommandObjectProcessKill : public CommandObjectParsed {
 // CommandObjectProcessSaveCore
 #pragma mark CommandObjectProcessSaveCore
 
+static constexpr OptionEnumValueElement g_corefile_save_style[] = {
+    {eSaveCoreFull, "full", "Create a core file with all memory saved"},
+    {eSaveCoreDirtyOnly, "modified-memory",
+     "Create a corefile with only modified memory saved"}};
+
+static constexpr OptionEnumValues SaveCoreStyles() {
+  return OptionEnumValues(g_corefile_save_style);
+}
+
+#define LLDB_OPTIONS_process_save_core
+#include "CommandOptions.inc"
+
 class CommandObjectProcessSaveCore : public CommandObjectParsed {
 public:
   CommandObjectProcessSaveCore(CommandInterpreter &interpreter)
       : CommandObjectParsed(interpreter, "process save-core",
                             "Save the current process as a core file using an "
                             "appropriate file type.",
-                            "process save-core FILE",
+                            "process save-core [-s corefile-style] FILE",
                             eCommandRequiresProcess | eCommandTryTargetAPILock |
                                 eCommandProcessMustBeLaunched) {}
 
   ~CommandObjectProcessSaveCore() override = default;
 
+  Options *GetOptions() override { return &m_options; }
+
+  class CommandOptions : public Options {
+  public:
+    CommandOptions()
+        : Options(), m_requested_save_core_style(eSaveCoreUnspecified) {}
+
+    ~CommandOptions() override = default;
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      return llvm::makeArrayRef(g_process_save_core_options);
+    }
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      const int short_option = m_getopt_table[option_idx].val;
+      Status error;
+
+      switch (short_option) {
+      case 's':
+        m_requested_save_core_style =
+            (lldb::SaveCoreStyle)OptionArgParser::ToOptionEnum(
+                option_arg, GetDefinitions()[option_idx].enum_values,
+                eSaveCoreUnspecified, error);
+        break;
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return {};
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      m_requested_save_core_style = eSaveCoreUnspecified;
+    }
+
+    // Instance variables to hold the values for command options.
+    SaveCoreStyle m_requested_save_core_style;
+  };
+
 protected:
   bool DoExecute(Args &command, CommandReturnObject &result) override {
     ProcessSP process_sp = m_exe_ctx.GetProcessSP();
     if (process_sp) {
       if (command.GetArgumentCount() == 1) {
         FileSpec output_file(command.GetArgumentAtIndex(0));
-        Status error = PluginManager::SaveCore(process_sp, output_file);
+        SaveCoreStyle corefile_style = m_options.m_requested_save_core_style;
+        Status error =
+            PluginManager::SaveCore(process_sp, output_file, corefile_style);
         if (error.Success()) {
+          if (corefile_style == SaveCoreStyle::eSaveCoreDirtyOnly) {
+            result.AppendMessageWithFormat(
+                "\nModified-memory only corefile "
+                "created.  This corefile may not show \n"
+                "library/framework/app binaries "
+                "on a 
diff erent system, or when \n"
+                "those binaries have "
+                "been updated/modified. Copies are not included\n"
+                "in this corefile.  Use --style full to include all "
+                "process memory.\n");
+          }
           result.SetStatus(eReturnStatusSuccessFinishResult);
         } else {
           result.AppendErrorWithFormat(
@@ -1197,6 +1262,8 @@ class CommandObjectProcessSaveCore : public CommandObjectParsed {
 
     return result.Succeeded();
   }
+
+  CommandOptions m_options;
 };
 
 // CommandObjectProcessStatus

diff  --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 1cc190ebc211e..6b5a33fb8b739 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -730,6 +730,12 @@ let Command = "process status" in {
     Desc<"Show verbose process status including extended crash information.">;
 }
 
+let Command = "process save_core" in {
+  def process_save_core_style : Option<"style", "s">, Group<1>,
+    EnumArg<"SaveCoreStyle", "SaveCoreStyles()">, Desc<"Request a specific style "
+    "of corefile to be saved.">;
+}
+
 let Command = "script import" in {
   def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>,
     Desc<"Allow the script to be loaded even if it was already loaded before. "

diff  --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index cb62e4e15b4eb..f03f2ca38e089 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -684,11 +684,13 @@ PluginManager::GetObjectFileCreateMemoryCallbackForPluginName(
 }
 
 Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp,
-                               const FileSpec &outfile) {
+                               const FileSpec &outfile,
+                               lldb::SaveCoreStyle &core_style) {
   Status error;
   auto &instances = GetObjectFileInstances().GetInstances();
   for (auto &instance : instances) {
-    if (instance.save_core && instance.save_core(process_sp, outfile, error))
+    if (instance.save_core &&
+        instance.save_core(process_sp, outfile, core_style, error))
       return error;
   }
   error.SetErrorString(

diff  --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index 09a8b05a77440..a6fba35cb576d 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -1126,7 +1126,8 @@ CommandObject::ArgumentTableEntry CommandObject::g_arguments_data[] = {
     { eArgRawInput, "raw-input", CommandCompletions::eNoCompletion, { nullptr, false }, "Free-form text passed to a command without prior interpretation, allowing spaces without requiring quotes.  To pass arguments and free form text put two dashes ' -- ' between the last argument and any raw input." },
     { eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command." },
     { eArgTypeColumnNum, "column", CommandCompletions::eNoCompletion, { nullptr, false }, "Column number in a source file." },
-    { eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." }
+    { eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." },
+    { eArgTypeSaveCoreStyle, "corefile-style", CommandCompletions::eNoCompletion, { nullptr, false }, "The type of corefile that lldb will try to create, dependant on this target's capabilities." }
     // clang-format on
 };
 

diff  --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 35898da280bde..eb32a2480e430 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -23,6 +23,7 @@
 #include "lldb/Host/Host.h"
 #include "lldb/Host/SafeMachO.h"
 #include "lldb/Symbol/DWARFCallFrameInfo.h"
+#include "lldb/Symbol/LocateSymbolFile.h"
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Target/DynamicLoader.h"
 #include "lldb/Target/MemoryRegionInfo.h"
@@ -6204,11 +6205,257 @@ bool ObjectFileMachO::SetLoadAddress(Target &target, lldb::addr_t value,
   return num_loaded_sections > 0;
 }
 
+struct all_image_infos_header {
+  uint32_t version;         // currently 1
+  uint32_t imgcount;        // number of binary images
+  uint64_t entries_fileoff; // file offset in the corefile of where the array of
+                            // struct entry's begin.
+  uint32_t entries_size;    // size of 'struct entry'.
+  uint32_t unused;
+};
+
+struct image_entry {
+  uint64_t filepath_offset;  // offset in corefile to c-string of the file path,
+                             // UINT64_MAX if unavailable.
+  uuid_t uuid;               // uint8_t[16].  should be set to all zeroes if
+                             // uuid is unknown.
+  uint64_t load_address;     // UINT64_MAX if unknown.
+  uint64_t seg_addrs_offset; // offset to the array of struct segment_vmaddr's.
+  uint32_t segment_count;    // The number of segments for this binary.
+  uint32_t unused;
+
+  image_entry() {
+    filepath_offset = UINT64_MAX;
+    memset(&uuid, 0, sizeof(uuid_t));
+    segment_count = 0;
+    load_address = UINT64_MAX;
+    seg_addrs_offset = UINT64_MAX;
+    unused = 0;
+  }
+  image_entry(const image_entry &rhs) {
+    filepath_offset = rhs.filepath_offset;
+    memcpy(&uuid, &rhs.uuid, sizeof(uuid_t));
+    segment_count = rhs.segment_count;
+    seg_addrs_offset = rhs.seg_addrs_offset;
+    load_address = rhs.load_address;
+    unused = rhs.unused;
+  }
+};
+
+struct segment_vmaddr {
+  char segname[16];
+  uint64_t vmaddr;
+  uint64_t unused;
+
+  segment_vmaddr() {
+    memset(&segname, 0, 16);
+    vmaddr = UINT64_MAX;
+    unused = 0;
+  }
+  segment_vmaddr(const segment_vmaddr &rhs) {
+    memcpy(&segname, &rhs.segname, 16);
+    vmaddr = rhs.vmaddr;
+    unused = rhs.unused;
+  }
+};
+
+// Write the payload for the "all image infos" LC_NOTE into
+// the supplied all_image_infos_payload, assuming that this
+// will be written into the corefile starting at
+// initial_file_offset.
+//
+// The placement of this payload is a little tricky.  We're
+// laying this out as
+//
+// 1. header (struct all_image_info_header)
+// 2. Array of fixed-size (struct image_entry)'s, one
+//    per binary image present in the process.
+// 3. Arrays of (struct segment_vmaddr)'s, a varying number
+//    for each binary image.
+// 4. Variable length c-strings of binary image filepaths,
+//    one per binary.
+//
+// To compute where everything will be laid out in the
+// payload, we need to iterate over the images and calculate
+// how many segment_vmaddr structures each image will need,
+// and how long each image's filepath c-string is. There
+// are some multiple passes over the image list while calculating
+// everything.
+
+static offset_t
+CreateAllImageInfosPayload(const lldb::ProcessSP &process_sp,
+                           offset_t initial_file_offset,
+                           StreamString &all_image_infos_payload) {
+  Target &target = process_sp->GetTarget();
+  const ModuleList &modules = target.GetImages();
+  size_t modules_count = modules.GetSize();
+
+  std::set<std::string> executing_uuids;
+  ThreadList &thread_list(process_sp->GetThreadList());
+  for (uint32_t i = 0; i < thread_list.GetSize(); i++) {
+    ThreadSP thread_sp = thread_list.GetThreadAtIndex(i);
+    uint32_t stack_frame_count = thread_sp->GetStackFrameCount();
+    for (uint32_t j = 0; j < stack_frame_count; j++) {
+      StackFrameSP stack_frame_sp = thread_sp->GetStackFrameAtIndex(j);
+      Address pc = stack_frame_sp->GetFrameCodeAddress();
+      ModuleSP module_sp = pc.GetModule();
+      if (module_sp) {
+        UUID uuid = module_sp->GetUUID();
+        if (uuid.IsValid()) {
+          executing_uuids.insert(uuid.GetAsString());
+        }
+      }
+    }
+  }
+
+  struct all_image_infos_header infos;
+  infos.version = 1;
+  infos.imgcount = modules_count;
+  infos.entries_size = sizeof(image_entry);
+  infos.entries_fileoff = initial_file_offset + sizeof(all_image_infos_header);
+  infos.unused = 0;
+
+  all_image_infos_payload.PutHex32(infos.version);
+  all_image_infos_payload.PutHex32(infos.imgcount);
+  all_image_infos_payload.PutHex64(infos.entries_fileoff);
+  all_image_infos_payload.PutHex32(infos.entries_size);
+  all_image_infos_payload.PutHex32(infos.unused);
+
+  // First create the structures for all of the segment name+vmaddr vectors
+  // for each module, so we will know the size of them as we add the
+  // module entries.
+  std::vector<std::vector<segment_vmaddr>> modules_segment_vmaddrs;
+  for (size_t i = 0; i < modules_count; i++) {
+    ModuleSP module = modules.GetModuleAtIndex(i);
+
+    SectionList *sections = module->GetSectionList();
+    size_t sections_count = sections->GetSize();
+    std::vector<segment_vmaddr> segment_vmaddrs;
+    for (size_t j = 0; j < sections_count; j++) {
+      SectionSP section = sections->GetSectionAtIndex(j);
+      if (!section->GetParent().get()) {
+        addr_t vmaddr = section->GetLoadBaseAddress(&target);
+        if (vmaddr == LLDB_INVALID_ADDRESS)
+          continue;
+        ConstString name = section->GetName();
+        segment_vmaddr seg_vmaddr;
+        strncpy(seg_vmaddr.segname, name.AsCString(),
+                sizeof(seg_vmaddr.segname));
+        seg_vmaddr.vmaddr = vmaddr;
+        seg_vmaddr.unused = 0;
+        segment_vmaddrs.push_back(seg_vmaddr);
+      }
+    }
+    modules_segment_vmaddrs.push_back(segment_vmaddrs);
+  }
+
+  offset_t size_of_vmaddr_structs = 0;
+  for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) {
+    size_of_vmaddr_structs +=
+        modules_segment_vmaddrs[i].size() * sizeof(segment_vmaddr);
+  }
+
+  offset_t size_of_filepath_cstrings = 0;
+  for (size_t i = 0; i < modules_count; i++) {
+    ModuleSP module_sp = modules.GetModuleAtIndex(i);
+    size_of_filepath_cstrings += module_sp->GetFileSpec().GetPath().size() + 1;
+  }
+
+  // Calculate the file offsets of our "all image infos" payload in the
+  // corefile. initial_file_offset the original value passed in to this method.
+
+  offset_t start_of_entries =
+      initial_file_offset + sizeof(all_image_infos_header);
+  offset_t start_of_seg_vmaddrs =
+      start_of_entries + sizeof(image_entry) * modules_count;
+  offset_t start_of_filenames = start_of_seg_vmaddrs + size_of_vmaddr_structs;
+
+  offset_t final_file_offset = start_of_filenames + size_of_filepath_cstrings;
+
+  // Now write the one-per-module 'struct image_entry' into the
+  // StringStream; keep track of where the struct segment_vmaddr
+  // entries for each module will end up in the corefile.
+
+  offset_t current_string_offset = start_of_filenames;
+  offset_t current_segaddrs_offset = start_of_seg_vmaddrs;
+  std::vector<struct image_entry> image_entries;
+  for (size_t i = 0; i < modules_count; i++) {
+    ModuleSP module_sp = modules.GetModuleAtIndex(i);
+
+    struct image_entry ent;
+    memcpy(&ent.uuid, module_sp->GetUUID().GetBytes().data(), sizeof(ent.uuid));
+    if (modules_segment_vmaddrs[i].size() > 0) {
+      ent.segment_count = modules_segment_vmaddrs[i].size();
+      ent.seg_addrs_offset = current_segaddrs_offset;
+    }
+    ent.filepath_offset = current_string_offset;
+    ObjectFile *objfile = module_sp->GetObjectFile();
+    if (objfile) {
+      Address base_addr(objfile->GetBaseAddress());
+      if (base_addr.IsValid()) {
+        ent.load_address = base_addr.GetLoadAddress(&target);
+      }
+    }
+
+    all_image_infos_payload.PutHex64(ent.filepath_offset);
+    all_image_infos_payload.PutRawBytes(ent.uuid, sizeof(ent.uuid));
+    all_image_infos_payload.PutHex64(ent.load_address);
+    all_image_infos_payload.PutHex64(ent.seg_addrs_offset);
+    all_image_infos_payload.PutHex32(ent.segment_count);
+
+    if (executing_uuids.find(module_sp->GetUUID().GetAsString()) !=
+        executing_uuids.end())
+      all_image_infos_payload.PutHex32(1);
+    else
+      all_image_infos_payload.PutHex32(0);
+
+    current_segaddrs_offset += ent.segment_count * sizeof(segment_vmaddr);
+    current_string_offset += module_sp->GetFileSpec().GetPath().size() + 1;
+  }
+
+  // Now write the struct segment_vmaddr entries into the StringStream.
+
+  for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) {
+    if (modules_segment_vmaddrs[i].size() == 0)
+      continue;
+    for (struct segment_vmaddr segvm : modules_segment_vmaddrs[i]) {
+      all_image_infos_payload.PutRawBytes(segvm.segname, sizeof(segvm.segname));
+      all_image_infos_payload.PutHex64(segvm.vmaddr);
+      all_image_infos_payload.PutHex64(segvm.unused);
+    }
+  }
+
+  for (size_t i = 0; i < modules_count; i++) {
+    ModuleSP module_sp = modules.GetModuleAtIndex(i);
+    std::string filepath = module_sp->GetFileSpec().GetPath();
+    all_image_infos_payload.PutRawBytes(filepath.data(), filepath.size() + 1);
+  }
+
+  return final_file_offset;
+}
+
+// Temp struct used to combine contiguous memory regions with
+// identical permissions.
+struct page_object {
+  addr_t addr;
+  addr_t size;
+  uint32_t prot;
+};
+
 bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
-                               const FileSpec &outfile, Status &error) {
+                               const FileSpec &outfile,
+                               lldb::SaveCoreStyle &core_style, Status &error) {
   if (!process_sp)
     return false;
 
+  // For Mach-O, we can only create full corefiles or dirty-page-only
+  // corefiles.  The default is dirty-page-only.
+  if (core_style != SaveCoreStyle::eSaveCoreFull) {
+    core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
+  } else {
+    core_style = SaveCoreStyle::eSaveCoreFull;
+  }
+
   Target &target = process_sp->GetTarget();
   const ArchSpec target_arch = target.GetArchitecture();
   const llvm::Triple &target_triple = target_arch.GetTriple();
@@ -6242,14 +6489,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
       Status range_error = process_sp->GetMemoryRegionInfo(0, range_info);
       const uint32_t addr_byte_size = target_arch.GetAddressByteSize();
       const ByteOrder byte_order = target_arch.GetByteOrder();
+      std::vector<page_object> pages_to_copy;
+
       if (range_error.Success()) {
         while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) {
-          const addr_t addr = range_info.GetRange().GetRangeBase();
-          const addr_t size = range_info.GetRange().GetByteSize();
-
-          if (size == 0)
-            break;
-
           // Calculate correct protections
           uint32_t prot = 0;
           if (range_info.GetReadable() == MemoryRegionInfo::eYes)
@@ -6259,32 +6502,28 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
           if (range_info.GetExecutable() == MemoryRegionInfo::eYes)
             prot |= VM_PROT_EXECUTE;
 
+          const addr_t addr = range_info.GetRange().GetRangeBase();
+          const addr_t size = range_info.GetRange().GetByteSize();
+
+          if (size == 0)
+            break;
+
           if (prot != 0) {
-            uint32_t cmd_type = LC_SEGMENT_64;
-            uint32_t segment_size = sizeof(llvm::MachO::segment_command_64);
-            if (addr_byte_size == 4) {
-              cmd_type = LC_SEGMENT;
-              segment_size = sizeof(llvm::MachO::segment_command);
+            addr_t pagesize = range_info.GetPageSize();
+            const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
+                range_info.GetDirtyPageList();
+            if (core_style == SaveCoreStyle::eSaveCoreDirtyOnly &&
+                dirty_page_list.hasValue()) {
+              core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
+              for (addr_t dirtypage : dirty_page_list.getValue()) {
+                page_object obj = {
+                    .addr = dirtypage, .size = pagesize, .prot = prot};
+                pages_to_copy.push_back(obj);
+              }
+            } else {
+              page_object obj = {.addr = addr, .size = size, .prot = prot};
+              pages_to_copy.push_back(obj);
             }
-            llvm::MachO::segment_command_64 segment = {
-                cmd_type,     // uint32_t cmd;
-                segment_size, // uint32_t cmdsize;
-                {0},          // char segname[16];
-                addr, // uint64_t vmaddr;    // uint32_t for 32-bit Mach-O
-                size, // uint64_t vmsize;    // uint32_t for 32-bit Mach-O
-                0,    // uint64_t fileoff;   // uint32_t for 32-bit Mach-O
-                size, // uint64_t filesize;  // uint32_t for 32-bit Mach-O
-                prot, // uint32_t maxprot;
-                prot, // uint32_t initprot;
-                0,    // uint32_t nsects;
-                0};   // uint32_t flags;
-            segment_load_commands.push_back(segment);
-          } else {
-            // No protections and a size of 1 used to be returned from old
-            // debugservers when we asked about a region that was past the
-            // last memory region and it indicates the end...
-            if (size == 1)
-              break;
           }
 
           range_error = process_sp->GetMemoryRegionInfo(
@@ -6293,6 +6532,51 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
             break;
         }
 
+        // Combine contiguous entries that have the same
+        // protections so we don't have an excess of
+        // load commands.
+        std::vector<page_object> combined_page_objects;
+        page_object last_obj;
+        last_obj.addr = LLDB_INVALID_ADDRESS;
+        for (page_object obj : pages_to_copy) {
+          if (last_obj.addr == LLDB_INVALID_ADDRESS) {
+            last_obj = obj;
+            continue;
+          }
+          if (last_obj.addr + last_obj.size == obj.addr &&
+              last_obj.prot == obj.prot) {
+            last_obj.size += obj.size;
+            continue;
+          }
+          combined_page_objects.push_back(last_obj);
+          last_obj = obj;
+        }
+
+        for (page_object obj : combined_page_objects) {
+          uint32_t cmd_type = LC_SEGMENT_64;
+          uint32_t segment_size = sizeof(llvm::MachO::segment_command_64);
+          if (addr_byte_size == 4) {
+            cmd_type = LC_SEGMENT;
+            segment_size = sizeof(llvm::MachO::segment_command);
+          }
+          llvm::MachO::segment_command_64 segment = {
+              cmd_type,     // uint32_t cmd;
+              segment_size, // uint32_t cmdsize;
+              {0},          // char segname[16];
+              obj.addr,     // uint64_t vmaddr;    // uint32_t for 32-bit
+                            // Mach-O
+              obj.size,     // uint64_t vmsize;    // uint32_t for 32-bit
+                            // Mach-O
+              0,            // uint64_t fileoff;   // uint32_t for 32-bit Mach-O
+              obj.size,     // uint64_t filesize;  // uint32_t for 32-bit
+                            // Mach-O
+              obj.prot,     // uint32_t maxprot;
+              obj.prot,     // uint32_t initprot;
+              0,            // uint32_t nsects;
+              0};           // uint32_t flags;
+          segment_load_commands.push_back(segment);
+        }
+
         StreamString buffer(Stream::eBinary, addr_byte_size, byte_order);
 
         llvm::MachO::mach_header_64 mach_header;
@@ -6363,6 +6647,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
           mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize();
         }
 
+        // LC_NOTE "all image infos"
+        mach_header.ncmds++;
+        mach_header.sizeofcmds += sizeof(llvm::MachO::note_command);
+
         // Write the mach header
         buffer.PutHex32(mach_header.magic);
         buffer.PutHex32(mach_header.cputype);
@@ -6378,10 +6666,33 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
         // Skip the mach header and all load commands and align to the next
         // 0x1000 byte boundary
         addr_t file_offset = buffer.GetSize() + mach_header.sizeofcmds;
-        if (file_offset & 0x00000fff) {
-          file_offset += 0x00001000ull;
-          file_offset &= (~0x00001000ull + 1);
-        }
+
+        file_offset = llvm::alignTo(file_offset, 16);
+
+        // Create the "all image infos" LC_NOTE payload
+        StreamString all_image_infos_payload(Stream::eBinary, addr_byte_size,
+                                             byte_order);
+        offset_t all_image_infos_payload_start = file_offset;
+        file_offset = CreateAllImageInfosPayload(process_sp, file_offset,
+                                                 all_image_infos_payload);
+
+        // Add the "all image infos" LC_NOTE load command
+        llvm::MachO::note_command all_image_info_note = {
+            LC_NOTE,                           /* uint32_t cmd */
+            sizeof(llvm::MachO::note_command), /* uint32_t cmdsize */
+            "all image infos",                 /* char data_owner[16] */
+            all_image_infos_payload_start,     /* uint64_t offset */
+            file_offset - all_image_infos_payload_start /* uint64_t size */
+        };
+        buffer.PutHex32(all_image_info_note.cmd);
+        buffer.PutHex32(all_image_info_note.cmdsize);
+        buffer.PutRawBytes(all_image_info_note.data_owner,
+                           sizeof(all_image_info_note.data_owner));
+        buffer.PutHex64(all_image_info_note.offset);
+        buffer.PutHex64(all_image_info_note.size);
+
+        // Align to 4096-byte page boundary for the LC_SEGMENTs.
+        file_offset = llvm::alignTo(file_offset, 4096);
 
         for (auto &segment : segment_load_commands) {
           segment.fileoff = file_offset;
@@ -6398,14 +6709,6 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
 
         // Write out all of the segment load commands
         for (const auto &segment : segment_load_commands) {
-          printf("0x%8.8x 0x%8.8x [0x%16.16" PRIx64 " - 0x%16.16" PRIx64
-                 ") [0x%16.16" PRIx64 " 0x%16.16" PRIx64
-                 ") 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x]\n",
-                 segment.cmd, segment.cmdsize, segment.vmaddr,
-                 segment.vmaddr + segment.vmsize, segment.fileoff,
-                 segment.filesize, segment.maxprot, segment.initprot,
-                 segment.nsects, segment.flags);
-
           buffer.PutHex32(segment.cmd);
           buffer.PutHex32(segment.cmdsize);
           buffer.PutRawBytes(segment.segname, sizeof(segment.segname));
@@ -6440,6 +6743,20 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
           error =
               core_file.get()->Write(buffer.GetString().data(), bytes_written);
           if (error.Success()) {
+
+            if (core_file.get()->SeekFromStart(all_image_info_note.offset) ==
+                -1) {
+              error.SetErrorStringWithFormat(
+                  "Unable to seek to corefile pos to write all iamge infos");
+              return false;
+            }
+
+            bytes_written = all_image_infos_payload.GetString().size();
+            error = core_file.get()->Write(
+                all_image_infos_payload.GetString().data(), bytes_written);
+            if (!error.Success())
+              return false;
+
             // Now write the file data for all memory segments in the process
             for (const auto &segment : segment_load_commands) {
               if (core_file.get()->SeekFromStart(segment.fileoff) == -1) {
@@ -6449,9 +6766,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
                 break;
               }
 
-              printf("Saving %" PRId64
-                     " bytes of data for memory region at 0x%" PRIx64 "\n",
-                     segment.vmsize, segment.vmaddr);
+              target.GetDebugger().GetAsyncOutputStream()->Printf(
+                  "Saving %" PRId64
+                  " bytes of data for memory region at 0x%" PRIx64 "\n",
+                  segment.vmsize, segment.vmaddr);
               addr_t bytes_left = segment.vmsize;
               addr_t addr = segment.vmaddr;
               Status memory_read_error;
@@ -6493,3 +6811,121 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
   }
   return false;
 }
+
+ObjectFileMachO::MachOCorefileAllImageInfos
+ObjectFileMachO::GetCorefileAllImageInfos() {
+  MachOCorefileAllImageInfos image_infos;
+
+  // Look for an "all image infos" LC_NOTE.
+  lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic);
+  for (uint32_t i = 0; i < m_header.ncmds; ++i) {
+    const uint32_t cmd_offset = offset;
+    llvm::MachO::load_command lc;
+    if (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr)
+      break;
+    if (lc.cmd == LC_NOTE) {
+      char data_owner[17];
+      m_data.CopyData(offset, 16, data_owner);
+      data_owner[16] = '\0';
+      offset += 16;
+      uint64_t fileoff = m_data.GetU64_unchecked(&offset);
+      offset += 4; /* size unused */
+
+      if (strcmp("all image infos", data_owner) == 0) {
+        offset = fileoff;
+        // Read the struct all_image_infos_header.
+        uint32_t version = m_data.GetU32(&offset);
+        if (version != 1) {
+          return image_infos;
+        }
+        uint32_t imgcount = m_data.GetU32(&offset);
+        uint64_t entries_fileoff = m_data.GetU64(&offset);
+        offset += 4; // uint32_t entries_size;
+        offset += 4; // uint32_t unused;
+
+        offset = entries_fileoff;
+        for (uint32_t i = 0; i < imgcount; i++) {
+          // Read the struct image_entry.
+          offset_t filepath_offset = m_data.GetU64(&offset);
+          uuid_t uuid;
+          memcpy(&uuid, m_data.GetData(&offset, sizeof(uuid_t)),
+                 sizeof(uuid_t));
+          uint64_t load_address = m_data.GetU64(&offset);
+          offset_t seg_addrs_offset = m_data.GetU64(&offset);
+          uint32_t segment_count = m_data.GetU32(&offset);
+          uint32_t currently_executing = m_data.GetU32(&offset);
+
+          MachOCorefileImageEntry image_entry;
+          image_entry.filename = (const char *)m_data.GetCStr(&filepath_offset);
+          image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
+          image_entry.load_address = load_address;
+          image_entry.currently_executing = currently_executing;
+
+          offset_t seg_vmaddrs_offset = seg_addrs_offset;
+          for (uint32_t j = 0; j < segment_count; j++) {
+            char segname[17];
+            m_data.CopyData(seg_vmaddrs_offset, 16, segname);
+            segname[16] = '\0';
+            seg_vmaddrs_offset += 16;
+            uint64_t vmaddr = m_data.GetU64(&seg_vmaddrs_offset);
+            seg_vmaddrs_offset += 8; /* unused */
+
+            std::tuple<ConstString, addr_t> new_seg{ConstString(segname),
+                                                    vmaddr};
+            image_entry.segment_load_addresses.push_back(new_seg);
+          }
+          image_infos.all_image_infos.push_back(image_entry);
+        }
+      }
+    }
+    offset = cmd_offset + lc.cmdsize;
+  }
+
+  return image_infos;
+}
+
+bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) {
+  MachOCorefileAllImageInfos image_infos = GetCorefileAllImageInfos();
+  bool added_images = false;
+  if (image_infos.IsValid()) {
+    for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) {
+      ModuleSpec module_spec;
+      module_spec.GetUUID() = image.uuid;
+      module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
+      if (image.currently_executing) {
+        Symbols::DownloadObjectAndSymbolFile(module_spec, true);
+        if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
+          process.GetTarget().GetOrCreateModule(module_spec, false);
+        }
+      }
+      Status error;
+      ModuleSP module_sp =
+          process.GetTarget().GetOrCreateModule(module_spec, false, &error);
+      if (!module_sp.get() || !module_sp->GetObjectFile()) {
+        if (image.load_address != LLDB_INVALID_ADDRESS) {
+          module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(),
+                                                   image.load_address);
+        }
+      }
+      if (module_sp.get() && module_sp->GetObjectFile()) {
+        added_images = true;
+        if (module_sp->GetObjectFile()->GetType() ==
+            ObjectFile::eTypeExecutable) {
+          process.GetTarget().SetExecutableModule(module_sp, eLoadDependentsNo);
+        }
+        for (auto name_vmaddr_tuple : image.segment_load_addresses) {
+          SectionList *sectlist = module_sp->GetObjectFile()->GetSectionList();
+          if (sectlist) {
+            SectionSP sect_sp =
+                sectlist->FindSectionByName(std::get<0>(name_vmaddr_tuple));
+            if (sect_sp) {
+              process.GetTarget().SetSectionLoadAddress(
+                  sect_sp, std::get<1>(name_vmaddr_tuple));
+            }
+          }
+        }
+      }
+    }
+  }
+  return added_images;
+}

diff  --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
index 2308c496c4216..8c6aac1400522 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
@@ -58,6 +58,7 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
 
   static bool SaveCore(const lldb::ProcessSP &process_sp,
                        const lldb_private::FileSpec &outfile,
+                       lldb::SaveCoreStyle &core_style,
                        lldb_private::Status &error);
 
   static bool MagicBytesMatch(lldb::DataBufferSP &data_sp, lldb::addr_t offset,
@@ -116,6 +117,8 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
                                  lldb_private::UUID &uuid,
                                  ObjectFile::BinaryType &type) override;
 
+  bool LoadCoreFileImages(lldb_private::Process &process) override;
+
   lldb::RegisterContextSP
   GetThreadContextAtIndex(uint32_t idx, lldb_private::Thread &thread) override;
 
@@ -209,6 +212,31 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
 
   bool SectionIsLoadable(const lldb_private::Section *section);
 
+  /// A corefile may include metadata about all of the binaries that were
+  /// present in the process when the corefile was taken.  This is only
+  /// implemented for Mach-O files for now; we'll generalize it when we
+  /// have other systems that can include the same.
+  struct MachOCorefileImageEntry {
+    std::string filename;
+    lldb_private::UUID uuid;
+    lldb::addr_t load_address = LLDB_INVALID_ADDRESS;
+    bool currently_executing;
+    std::vector<std::tuple<lldb_private::ConstString, lldb::addr_t>>
+        segment_load_addresses;
+  };
+
+  struct MachOCorefileAllImageInfos {
+    std::vector<MachOCorefileImageEntry> all_image_infos;
+    bool IsValid() { return all_image_infos.size() > 0; }
+  };
+
+  /// Get the list of binary images that were present in the process
+  /// when the corefile was produced.
+  /// \return
+  ///     The MachOCorefileAllImageInfos object returned will have
+  ///     IsValid() == false if the information is unavailable.
+  MachOCorefileAllImageInfos GetCorefileAllImageInfos();
+
   llvm::MachO::mach_header m_header;
   static lldb_private::ConstString GetSegmentNameTEXT();
   static lldb_private::ConstString GetSegmentNameDATA();

diff  --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index 4e2598c824400..050cb0d492476 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -189,7 +189,9 @@ size_t ObjectFilePECOFF::GetModuleSpecifications(
 
 bool ObjectFilePECOFF::SaveCore(const lldb::ProcessSP &process_sp,
                                 const lldb_private::FileSpec &outfile,
+                                lldb::SaveCoreStyle &core_style,
                                 lldb_private::Status &error) {
+  core_style = eSaveCoreFull;
   return SaveMiniDump(process_sp, outfile, error);
 }
 

diff  --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
index fdcacbeab1eed..9fe4a1349ae84 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
@@ -79,6 +79,7 @@ class ObjectFilePECOFF : public lldb_private::ObjectFile {
 
   static bool SaveCore(const lldb::ProcessSP &process_sp,
                        const lldb_private::FileSpec &outfile,
+                       lldb::SaveCoreStyle &core_style,
                        lldb_private::Status &error);
 
   static bool MagicBytesMatch(lldb::DataBufferSP &data_sp);

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 0abc80250d48f..78da9a060a754 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -16,6 +16,7 @@
 
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Host/StringConvert.h"
 #include "lldb/Host/XML.h"
 #include "lldb/Symbol/Symbol.h"
 #include "lldb/Target/MemoryRegionInfo.h"
@@ -285,6 +286,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
     m_gdb_server_name.clear();
     m_gdb_server_version = UINT32_MAX;
     m_default_packet_timeout = seconds(0);
+    m_target_vm_page_size = 0;
     m_max_packet_size = 0;
     m_qSupported_response.clear();
     m_supported_async_json_packets_is_valid = false;
@@ -1192,6 +1194,12 @@ bool GDBRemoteCommunicationClient::GetHostInfo(bool force) {
               SetPacketTimeout(m_default_packet_timeout);
               ++num_keys_decoded;
             }
+          } else if (name.equals("vm-page-size")) {
+            int page_size;
+            if (!value.getAsInteger(0, page_size)) {
+              m_target_vm_page_size = page_size;
+              ++num_keys_decoded;
+            }
           }
         }
 
@@ -1503,9 +1511,30 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
           // Now convert the HEX bytes into a string value
           error_extractor.GetHexByteString(error_string);
           error.SetErrorString(error_string.c_str());
+        } else if (name.equals("dirty-pages")) {
+          std::vector<addr_t> dirty_page_list;
+          std::string comma_sep_str = value.str();
+          size_t comma_pos;
+          addr_t page;
+          while ((comma_pos = comma_sep_str.find(',')) != std::string::npos) {
+            comma_sep_str[comma_pos] = '\0';
+            page = StringConvert::ToUInt64(comma_sep_str.c_str(),
+                                           LLDB_INVALID_ADDRESS, 16);
+            if (page != LLDB_INVALID_ADDRESS)
+              dirty_page_list.push_back(page);
+            comma_sep_str.erase(0, comma_pos + 1);
+          }
+          page = StringConvert::ToUInt64(comma_sep_str.c_str(),
+                                         LLDB_INVALID_ADDRESS, 16);
+          if (page != LLDB_INVALID_ADDRESS)
+            dirty_page_list.push_back(page);
+          region_info.SetDirtyPageList(dirty_page_list);
         }
       }
 
+      if (m_target_vm_page_size != 0)
+        region_info.SetPageSize(m_target_vm_page_size);
+
       if (region_info.GetRange().IsValid()) {
         // We got a valid address range back but no permissions -- which means
         // this is an unmapped page

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 975baa2f08df2..50ac5c5fccdfa 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -592,6 +592,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
       UINT32_MAX; // from reply to qGDBServerVersion, zero if
                   // qGDBServerVersion is not supported
   std::chrono::seconds m_default_packet_timeout;
+  int m_target_vm_page_size = 0; // target system VM page size; 0 unspecified
   uint64_t m_max_packet_size = 0;    // as returned by qSupported
   std::string m_qSupported_response; // the complete response to qSupported
 

diff  --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
index 68c63b8902397..b5dc502473694 100644
--- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
+++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
@@ -332,7 +332,6 @@ Status ProcessMachCore::DoLoadCore() {
     m_core_range_infos.Sort();
   }
 
-
   bool found_main_binary_definitively = false;
 
   addr_t objfile_binary_addr;
@@ -414,6 +413,14 @@ Status ProcessMachCore::DoLoadCore() {
     }
   }
 
+  // If we have a "all image infos" LC_NOTE, try to load all of the
+  // binaries listed, and set their Section load addresses in the Target.
+  if (found_main_binary_definitively == false &&
+      core_objfile->LoadCoreFileImages(*this)) {
+    m_dyld_plugin_name = DynamicLoaderDarwinKernel::GetPluginNameStatic();
+    found_main_binary_definitively = true;
+  }
+
   if (!found_main_binary_definitively &&
       (m_dyld_addr == LLDB_INVALID_ADDRESS ||
        m_mach_kernel_addr == LLDB_INVALID_ADDRESS)) {

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
new file mode 100644
index 0000000000000..38ff267d2c68c
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
@@ -0,0 +1,65 @@
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from gdbclientutils import *
+
+
+class TestMemoryRegionDirtyPages(GDBRemoteTestBase):
+
+    @skipIfXmlSupportMissing
+    def test(self):
+        class MyResponder(MockGDBServerResponder):
+
+            def qHostInfo(self):
+                return "ptrsize:8;endian:little;vm-page-size:4096;"
+
+            def qMemoryRegionInfo(self, addr):
+                if addr == 0:
+                    return "start:0;size:100000000;"
+                if addr == 0x100000000:
+                    return "start:100000000;size:4000;permissions:rx;dirty-pages:;"
+                if addr == 0x100004000:
+                    return "start:100004000;size:4000;permissions:r;dirty-pages:0x100004000;"
+                if addr == 0x1000a2000:
+                    return "start:1000a2000;size:5000;permissions:r;dirty-pages:0x1000a2000,0x1000a3000,0x1000a4000,0x1000a5000,0x1000a6000;"
+
+        self.server.responder = MyResponder()
+        target = self.dbg.CreateTarget('')
+        if self.TraceOn():
+          self.runCmd("log enable gdb-remote packets")
+          self.addTearDownHook(
+                lambda: self.runCmd("log disable gdb-remote packets"))
+        process = self.connect(target)
+
+        # A memory region where we don't know anything about dirty pages
+        region = lldb.SBMemoryRegionInfo()
+        err = process.GetMemoryRegionInfo(0, region)
+        self.assertTrue(err.Success())
+        self.assertFalse(region.HasDirtyMemoryPageList())
+        self.assertEqual(region.GetNumDirtyPages(), 0)
+        region.Clear()
+
+        # A memory region with dirty page information -- and zero dirty pages
+        err = process.GetMemoryRegionInfo(0x100000000, region)
+        self.assertTrue(err.Success())
+        self.assertTrue(region.HasDirtyMemoryPageList())
+        self.assertEqual(region.GetNumDirtyPages(), 0)
+        self.assertEqual(region.GetPageSize(), 4096)
+        region.Clear()
+
+        # A memory region with one dirty page
+        err = process.GetMemoryRegionInfo(0x100004000, region)
+        self.assertTrue(err.Success())
+        self.assertTrue(region.HasDirtyMemoryPageList())
+        self.assertEqual(region.GetNumDirtyPages(), 1)
+        self.assertEqual(region.GetDirtyPageAddressAtIndex(0), 0x100004000)
+        region.Clear()
+
+        # A memory region with multple dirty pages
+        err = process.GetMemoryRegionInfo(0x1000a2000, region)
+        self.assertTrue(err.Success())
+        self.assertTrue(region.HasDirtyMemoryPageList())
+        self.assertEqual(region.GetNumDirtyPages(), 5)
+        self.assertEqual(region.GetDirtyPageAddressAtIndex(4), 0x1000a6000)
+        region.Clear()
+

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
index 4c5ff03ef5a42..c683023d13baa 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
@@ -166,7 +166,7 @@ def respond(self, packet):
         if packet == "QListThreadsInStopReply":
             return self.QListThreadsInStopReply()
         if packet.startswith("qMemoryRegionInfo:"):
-            return self.qMemoryRegionInfo()
+            return self.qMemoryRegionInfo(int(packet.split(':')[1], 16))
         if packet == "qQueryGDBServer":
             return self.qQueryGDBServer()
         if packet == "qHostInfo":
@@ -282,7 +282,7 @@ def QThreadSuffixSupported(self):
     def QListThreadsInStopReply(self):
         return ""
 
-    def qMemoryRegionInfo(self):
+    def qMemoryRegionInfo(self, addr):
         return ""
 
     def qPathComplete(self):

diff  --git a/lldb/test/API/macosx/skinny-corefile/Makefile b/lldb/test/API/macosx/skinny-corefile/Makefile
new file mode 100644
index 0000000000000..efe37f3d2b8b2
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/Makefile
@@ -0,0 +1,15 @@
+LD_EXTRAS = -L. -lto-be-removed -lpresent
+C_SOURCES = main.c
+
+include Makefile.rules
+
+a.out: libto-be-removed libpresent
+
+libto-be-removed: libpresent
+	$(MAKE) -f $(MAKEFILE_RULES) \
+	  DYLIB_ONLY=YES DYLIB_C_SOURCES=to-be-removed.c DYLIB_NAME=to-be-removed \
+	  LD_EXTRAS="-L. -lpresent"
+
+libpresent:
+	$(MAKE) -f $(MAKEFILE_RULES) \
+	  DYLIB_ONLY=YES DYLIB_C_SOURCES=present.c DYLIB_NAME=present 

diff  --git a/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py b/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
new file mode 100644
index 0000000000000..c18ef44056425
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
@@ -0,0 +1,162 @@
+"""Test that lldb can create a skinny corefile, and load all available libraries correctly."""
+
+
+
+import os
+import re
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestFirmwareCorefiles(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipIf(debug_info=no_match(["dsym"]), bugnumber="This test is looking explicitly for a dSYM")
+    @skipUnlessDarwin
+    def test_lc_note(self):
+        self.build()
+        self.aout_exe = self.getBuildArtifact("a.out")
+        self.aout_dsym = self.getBuildArtifact("a.out.dSYM")
+        self.to_be_removed_dylib = self.getBuildArtifact("libto-be-removed.dylib")
+        self.to_be_removed_dsym = self.getBuildArtifact("libto-be-removed.dylib.dSYM")
+        self.corefile = self.getBuildArtifact("process.core")
+        self.dsym_for_uuid = self.getBuildArtifact("dsym-for-uuid.sh")
+
+        # After the corefile is created, we'll move a.out and a.out.dSYM 
+        # into hide.noindex and lldb will have to use the 
+        # LLDB_APPLE_DSYMFORUUID_EXECUTABLE script to find them.
+        self.hide_dir = self.getBuildArtifact("hide.noindex")
+        lldbutil.mkdir_p(self.hide_dir)
+        self.hide_aout_exe = self.getBuildArtifact("hide.noindex/a.out")
+        self.hide_aout_dsym = self.getBuildArtifact("hide.noindex/a.out.dSYM")
+
+        # We can hook in our dsym-for-uuid shell script to lldb with 
+        # this env var instead of requiring a defaults write.
+        os.environ['LLDB_APPLE_DSYMFORUUID_EXECUTABLE'] = self.dsym_for_uuid
+        self.addTearDownHook(lambda: os.environ.pop('LLDB_APPLE_DSYMFORUUID_EXECUTABLE', None))
+
+        dwarfdump_uuid_regex = re.compile(
+            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
+        dwarfdump_cmd_output = subprocess.check_output(
+                ('/usr/bin/dwarfdump --uuid "%s"' % self.aout_exe), shell=True).decode("utf-8")
+        aout_uuid = None
+        for line in dwarfdump_cmd_output.splitlines():
+            match = dwarfdump_uuid_regex.search(line)
+            if match:
+                aout_uuid = match.group(1)
+        self.assertNotEqual(aout_uuid, None, "Could not get uuid of built a.out")
+
+        ###  Create our dsym-for-uuid shell script which returns self.hide_aout_exe.
+        shell_cmds = [
+                '#! /bin/sh',
+                '# the last argument is the uuid',
+                'while [ $# -gt 1 ]',
+                'do',
+                '  shift',
+                'done',
+                'ret=0',
+                'echo "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>"',
+                'echo "<!DOCTYPE plist PUBLIC \\"-//Apple//DTD PLIST 1.0//EN\\" \\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\">"',
+                'echo "<plist version=\\"1.0\\">"',
+                '',
+                'if [ "$1" = "%s" ]' % aout_uuid,
+                'then',
+                '  uuid=%s' % aout_uuid,
+                '  bin=%s' % self.hide_aout_exe,
+                '  dsym=%s.dSYM/Contents/Resources/DWARF/%s' % (self.hide_aout_exe, os.path.basename(self.hide_aout_exe)),
+                'fi',
+                'if [ -z "$uuid" -o -z "$bin" -o ! -f "$bin" ]',
+                'then',
+                '  echo "<key>DBGError</key><string>not found</string>"',
+                '  echo "</plist>"', 
+                '  exit 1',
+                'fi',
+                'echo "<dict><key>$uuid</key><dict>"',
+                '',
+                'echo "<key>DBGArchitecture</key><string>x86_64</string>"',
+                'echo "<key>DBGDSYMPath</key><string>$dsym</string>"',
+                'echo "<key>DBGSymbolRichExecutable</key><string>$bin</string>"',
+                'echo "</dict></dict></plist>"',
+                'exit $ret'
+                ]
+
+        with open(self.dsym_for_uuid, "w") as writer:
+            for l in shell_cmds:
+                writer.write(l + '\n')
+
+        os.chmod(self.dsym_for_uuid, 0o755)
+
+
+        # Launch a live process with a.out, libto-be-removed.dylib, 
+        # libpresent.dylib all in their original locations, create
+        # a corefile at the breakpoint.
+        (target, process, t, bp) = lldbutil.run_to_source_breakpoint (
+                self, "break here", lldb.SBFileSpec('present.c'))
+
+        self.assertTrue(process.IsValid())
+
+        if self.TraceOn():
+            self.runCmd("bt")
+            self.runCmd("image list")
+
+        self.runCmd("process save-core " + self.corefile)
+        process.Kill()
+        target.Clear()
+
+        # Move the main binary and its dSYM into the hide.noindex
+        # directory.  Now the only way lldb can find them is with
+        # the LLDB_APPLE_DSYMFORUUID_EXECUTABLE shell script -
+        # so we're testing that this dSYM discovery method works.
+        os.rename(self.aout_exe, self.hide_aout_exe)
+        os.rename(self.aout_dsym, self.hide_aout_dsym)
+
+        # Completely remove the libto-be-removed.dylib, so we're
+        # testing that lldb handles an unavailable binary correctly,
+        # and non-dirty memory from this binary (e.g. the executing
+        # instructions) are NOT included in the corefile.
+        os.unlink(self.to_be_removed_dylib)
+        shutil.rmtree(self.to_be_removed_dsym)
+
+
+        # Now load the corefile
+        self.target = self.dbg.CreateTarget('')
+        self.process = self.target.LoadCore(self.corefile)
+        self.assertTrue(self.process.IsValid())
+        if self.TraceOn():
+            self.runCmd("image list")
+            self.runCmd("bt")
+
+        self.assertTrue(self.process.IsValid())
+        self.assertTrue(self.process.GetSelectedThread().IsValid())
+
+        # f0 is present() in libpresent.dylib
+        f0 = self.process.GetSelectedThread().GetFrameAtIndex(0)
+        to_be_removed_dirty_data = f0.FindVariable("to_be_removed_dirty_data")
+        self.assertEqual(to_be_removed_dirty_data.GetValueAsUnsigned(), 20)
+
+        present_heap_buf = f0.FindVariable("present_heap_buf")
+        self.assertTrue("have ints 5 20 20 5" in present_heap_buf.GetSummary())
+
+
+        # f1 is to_be_removed() in libto-be-removed.dylib
+        # it has been removed since the corefile was created,
+        # and the instructions for this frame should NOT be included
+        # in the corefile.  They were not dirty pages.
+        f1 = self.process.GetSelectedThread().GetFrameAtIndex(1) 
+        err = lldb.SBError()
+        uint = self.process.ReadUnsignedFromMemory(f1.GetPC(), 4, err)
+        self.assertTrue(err.Fail())
+
+
+        # TODO Future testing could check that read-only constant data
+        # (main_const_data, present_const_data) can be read both as an
+        # SBValue and in an expression -- which means lldb needs to read
+        # them out of the binaries, they are not present in the corefile.
+        # And checking file-scope dirty data (main_dirty_data, 
+        # present_dirty_data) the same way would be good, instead of just
+        # checking the heap and stack like are being done right now.

diff  --git a/lldb/test/API/macosx/skinny-corefile/main.c b/lldb/test/API/macosx/skinny-corefile/main.c
new file mode 100644
index 0000000000000..f91de176f2a69
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/main.c
@@ -0,0 +1,20 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "present.h"
+#include "to-be-removed.h"
+
+const int main_const_data = 5;
+int main_dirty_data = 10;
+int main(int argc, char **argv) {
+
+  to_be_removed_init(argc);
+  present_init(argc);
+  main_dirty_data += argc;
+
+  char *heap_buf = (char *)malloc(80);
+  strcpy(heap_buf, "this is a string on the heap");
+
+  return to_be_removed(heap_buf, main_const_data, main_dirty_data);
+}

diff  --git a/lldb/test/API/macosx/skinny-corefile/present.c b/lldb/test/API/macosx/skinny-corefile/present.c
new file mode 100644
index 0000000000000..f6fa5f435379e
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/present.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "present.h"
+
+const int present_const_data = 5;
+int present_dirty_data = 10;
+
+void present_init(int in) { present_dirty_data += 10; }
+
+int present(char *to_be_removed_heap_buf, int to_be_removed_const_data,
+            int to_be_removed_dirty_data) {
+  char *present_heap_buf = (char *)malloc(256);
+  sprintf(present_heap_buf, "have ints %d %d %d %d", to_be_removed_const_data,
+          to_be_removed_dirty_data, present_dirty_data, present_const_data);
+  printf("%s\n", present_heap_buf);
+  puts(to_be_removed_heap_buf);
+
+  puts("break here");
+
+  return present_const_data + present_dirty_data;
+}

diff  --git a/lldb/test/API/macosx/skinny-corefile/present.h b/lldb/test/API/macosx/skinny-corefile/present.h
new file mode 100644
index 0000000000000..424a0c0b5585d
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/present.h
@@ -0,0 +1,2 @@
+void present_init (int in);
+int present (char *to_be_removed_heap_buf, int to_be_removed_const_data, int to_be_removed_dirty_data);

diff  --git a/lldb/test/API/macosx/skinny-corefile/to-be-removed.c b/lldb/test/API/macosx/skinny-corefile/to-be-removed.c
new file mode 100644
index 0000000000000..186f406205ba8
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/to-be-removed.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "present.h"
+#include "to-be-removed.h"
+
+const int to_be_removed_const_data = 5;
+int to_be_removed_dirty_data = 10;
+
+void to_be_removed_init(int in) { to_be_removed_dirty_data += 10; }
+
+int to_be_removed(char *main_heap_buf, int main_const_data,
+                  int main_dirty_data) {
+  char *to_be_removed_heap_buf = (char *)malloc(256);
+  sprintf(to_be_removed_heap_buf, "got string '%s' have int %d %d %d",
+          main_heap_buf, to_be_removed_dirty_data, main_const_data,
+          main_dirty_data);
+  printf("%s\n", to_be_removed_heap_buf);
+  return present(to_be_removed_heap_buf, to_be_removed_const_data,
+                 to_be_removed_dirty_data);
+}

diff  --git a/lldb/test/API/macosx/skinny-corefile/to-be-removed.h b/lldb/test/API/macosx/skinny-corefile/to-be-removed.h
new file mode 100644
index 0000000000000..3f20643ee8220
--- /dev/null
+++ b/lldb/test/API/macosx/skinny-corefile/to-be-removed.h
@@ -0,0 +1,2 @@
+void to_be_removed_init (int in);
+int to_be_removed (char *main_heap_buf, int main_const_data, int main_dirty_data);

diff  --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py
index e69116877d634..9332cc1163fb7 100644
--- a/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py
+++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py
@@ -30,6 +30,7 @@ class TestGdbRemoteHostInfo(GdbRemoteTestCaseBase):
         "ptrsize",
         "triple",
         "vendor",
+        "vm-page-size",
         "watchpoint_exceptions_received",
     ])
 

diff  --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h
index c6b894521a711..739d453c10d39 100644
--- a/lldb/tools/debugserver/source/DNBDefs.h
+++ b/lldb/tools/debugserver/source/DNBDefs.h
@@ -18,6 +18,7 @@
 #include <cstdio>
 #include <sys/syslimits.h>
 #include <unistd.h>
+#include <vector>
 
 // Define nub_addr_t and the invalid address value from the architecture
 #if defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__)
@@ -316,9 +317,12 @@ struct DNBExecutableImageInfo {
 };
 
 struct DNBRegionInfo {
+public:
+  DNBRegionInfo() : addr(0), size(0), permissions(0), dirty_pages() {}
   nub_addr_t addr;
   nub_addr_t size;
   uint32_t permissions;
+  std::vector<nub_addr_t> dirty_pages;
 };
 
 enum DNBProfileDataScanType {

diff  --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
index 2b039c7b16cb1..14220649876c0 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -72,6 +72,49 @@ nub_size_t MachVMMemory::MaxBytesLeftInPage(task_t task, nub_addr_t addr,
   return count;
 }
 
+#define MAX_STACK_ALLOC_DISPOSITIONS                                           \
+  (16 * 1024 / sizeof(int)) // 16K of allocations
+
+std::vector<nub_addr_t> get_dirty_pages(task_t task, mach_vm_address_t addr,
+                                        mach_vm_size_t size) {
+  std::vector<nub_addr_t> dirty_pages;
+
+  int pages_to_query = size / vm_page_size;
+  // Don't try to fetch too many pages' dispositions in a single call or we
+  // could blow our stack out.
+  mach_vm_size_t dispositions_size =
+      std::min(pages_to_query, (int)MAX_STACK_ALLOC_DISPOSITIONS);
+  int dispositions[dispositions_size];
+
+  mach_vm_size_t chunk_count =
+      ((pages_to_query + MAX_STACK_ALLOC_DISPOSITIONS - 1) /
+       MAX_STACK_ALLOC_DISPOSITIONS);
+
+  for (mach_vm_size_t cur_disposition_chunk = 0;
+       cur_disposition_chunk < chunk_count; cur_disposition_chunk++) {
+    mach_vm_size_t dispositions_already_queried =
+        cur_disposition_chunk * MAX_STACK_ALLOC_DISPOSITIONS;
+
+    mach_vm_size_t chunk_pages_to_query = std::min(
+        pages_to_query - dispositions_already_queried, dispositions_size);
+    mach_vm_address_t chunk_page_aligned_start_addr =
+        addr + (dispositions_already_queried * vm_page_size);
+
+    kern_return_t kr = mach_vm_page_range_query(
+        task, chunk_page_aligned_start_addr,
+        chunk_pages_to_query * vm_page_size, (mach_vm_address_t)dispositions,
+        &chunk_pages_to_query);
+    if (kr != KERN_SUCCESS)
+      return dirty_pages;
+    for (mach_vm_size_t i = 0; i < chunk_pages_to_query; i++) {
+      uint64_t dirty_addr = chunk_page_aligned_start_addr + (i * vm_page_size);
+      if (dispositions[i] & VM_PAGE_QUERY_PAGE_DIRTY)
+        dirty_pages.push_back(dirty_addr);
+    }
+  }
+  return dirty_pages;
+}
+
 nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
                                              DNBRegionInfo *region_info) {
   MachVMRegion vmRegion(task);
@@ -80,6 +123,8 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
     region_info->addr = vmRegion.StartAddress();
     region_info->size = vmRegion.GetByteSize();
     region_info->permissions = vmRegion.GetDNBPermissions();
+    region_info->dirty_pages =
+        get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize());
   } else {
     region_info->addr = address;
     region_info->size = 0;

diff  --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 9e89ab9539ec2..7273d355acf9e 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -19,6 +19,7 @@
 #include <libproc.h>
 #include <mach-o/loader.h>
 #include <mach/exception_types.h>
+#include <mach/mach_vm.h>
 #include <mach/task_info.h>
 #include <pwd.h>
 #include <sys/stat.h>
@@ -4447,7 +4448,7 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) {
         __FILE__, __LINE__, p, "Invalid address in qMemoryRegionInfo packet");
   }
 
-  DNBRegionInfo region_info = {0, 0, 0};
+  DNBRegionInfo region_info;
   DNBProcessMemoryRegionInfo(m_ctx.ProcessID(), address, &region_info);
   std::ostringstream ostrm;
 
@@ -4467,6 +4468,18 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) {
     if (region_info.permissions & eMemoryPermissionsExecutable)
       ostrm << 'x';
     ostrm << ';';
+
+    ostrm << "dirty-pages:";
+    if (region_info.dirty_pages.size() > 0) {
+      bool first = true;
+      for (nub_addr_t addr : region_info.dirty_pages) {
+        if (!first)
+          ostrm << ",";
+        first = false;
+        ostrm << "0x" << std::hex << addr;
+      }
+    }
+    ostrm << ";";
   }
   return SendPacket(ostrm.str());
 }
@@ -4993,6 +5006,8 @@ rnb_err_t RNBRemote::HandlePacket_qHostInfo(const char *p) {
   strm << "default_packet_timeout:10;";
 #endif
 
+  strm << "vm-page-size:" << std::dec << vm_page_size << ";";
+
   return SendPacket(strm.str());
 }
 


        


More information about the lldb-commits mailing list