Allow firmware binaries to be specified only by load address

Add support to Mach-O corefiles and to live gdb remote serial protocol
connections for the corefile/remote stub to provide a list of load
addresses of binaries that should be found & loaded by lldb, and nothing
else.  lldb will try to parse the binary out of memory, and if it can
find a UUID, try to find a binary & its debug information based on the
UUID, falling back to using the memory image if it must.

A bit of code unification from three parts of lldb that were loading
individual binaries already, so there is a shared method in
DynamicLoader to handle all of the variations they were doing.

Re-landing this with a uuid_is_null() implementation added to
Utility/UuidCompatibility.h for non-Darwin systems.

Differential Revision: https://reviews.llvm.org/D130813
rdar://94249937
rdar://94249384

GitOrigin-RevId: 96d12187b3d28f63d29802a7af49dfe53cc306f3
diff --git a/docs/lldb-gdb-remote.txt b/docs/lldb-gdb-remote.txt
index 820f3bd..497980e 100644
--- a/docs/lldb-gdb-remote.txt
+++ b/docs/lldb-gdb-remote.txt
@@ -1073,6 +1073,12 @@
 main-binary-address: is the load address of the firmware type binary
 main-binary-slide: is the slide of the firmware type binary, if address isn't known
 
+binary-addresses: A comma-separated list of binary load addresses base16.  
+                  lldb will parse the binaries in memory to get UUIDs, then
+                  try to find the binaries & debug info by UUID.  Intended for
+                  use with a small number of firmware type binaries where the 
+                  search for binary/debug info may be expensive.
+
 //----------------------------------------------------------------------
 // "qShlibInfoAddr"
 //
diff --git a/include/lldb/Target/DynamicLoader.h b/include/lldb/Target/DynamicLoader.h
index 397b4aa..7e7a421 100644
--- a/include/lldb/Target/DynamicLoader.h
+++ b/include/lldb/Target/DynamicLoader.h
@@ -210,6 +210,52 @@
                                              lldb::addr_t base_addr,
                                              bool base_addr_is_offset);
 
+  /// Find/load a binary into lldb given a UUID and the address where it is
+  /// loaded in memory, or a slide to be applied to the file address.
+  /// May force an expensive search on the computer to find the binary by
+  /// UUID, should not be used for a large number of binaries - intended for
+  /// an environment where there may be one, or a few, binaries resident in
+  /// memory.
+  ///
+  /// Given a UUID, search for a binary and load it at the address provided,
+  /// or with the slide applied, or at the file address unslid.
+  ///
+  /// Given an address, try to read the binary out of memory, get the UUID,
+  /// find the file if possible and load it unslid, or add the memory module.
+  ///
+  /// \param[in] process
+  ///     The process to add this binary to.
+  ///
+  /// \param[in] uuid
+  ///     UUID of the binary to be loaded.  UUID may be empty, and if a
+  ///     load address is supplied, will read the binary from memory, get
+  ///     a UUID and try to find a local binary.  There is a performance
+  ///     cost to doing this, it is not preferable.
+  ///
+  /// \param[in] value
+  ///     Address where the binary should be loaded, or read out of memory.
+  ///     Or a slide value, to be applied to the file addresses of the binary.
+  ///
+  /// \param[in] value_is_offset
+  ///     A flag indicating that \p value is an address, or an offset to
+  ///     be applied to the file addresses.
+  ///
+  /// \param[in] force_symbol_search
+  ///     Allow the search to do a possibly expensive external search for
+  ///     the ObjectFile and/or SymbolFile.
+  ///
+  /// \param[in] notify
+  ///     Whether ModulesDidLoad should be called when a binary has been added
+  ///     to the Target.  The caller may prefer to batch up these when loading
+  ///     multiple binaries.
+  ///
+  /// \return
+  ///     Returns a shared pointer for the Module that has been added.
+  static lldb::ModuleSP
+  LoadBinaryWithUUIDAndAddress(Process *process, UUID uuid, lldb::addr_t value,
+                               bool value_is_offset, bool force_symbol_search,
+                               bool notify);
+
   /// Get information about the shared cache for a process, if possible.
   ///
   /// On some systems (e.g. Darwin based systems), a set of libraries that are
diff --git a/source/Core/DynamicLoader.cpp b/source/Core/DynamicLoader.cpp
index 96e0d4e..1fe60e0 100644
--- a/source/Core/DynamicLoader.cpp
+++ b/source/Core/DynamicLoader.cpp
@@ -13,11 +13,15 @@
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/Section.h"
+#include "lldb/Symbol/LocateSymbolFile.h"
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Target/Platform.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
 #include "lldb/lldb-private-interfaces.h"
 
 #include "llvm/ADT/StringRef.h"
@@ -171,6 +175,100 @@
   return nullptr;
 }
 
+static ModuleSP ReadUnnamedMemoryModule(Process *process, addr_t addr) {
+  char namebuf[80];
+  snprintf(namebuf, sizeof(namebuf), "memory-image-0x%" PRIx64, addr);
+  return process->ReadModuleFromMemory(FileSpec(namebuf), addr);
+}
+
+ModuleSP DynamicLoader::LoadBinaryWithUUIDAndAddress(Process *process,
+                                                     UUID uuid, addr_t value,
+                                                     bool value_is_offset,
+                                                     bool force_symbol_search,
+                                                     bool notify) {
+  ModuleSP memory_module_sp;
+  ModuleSP module_sp;
+  PlatformSP platform_sp = process->GetTarget().GetPlatform();
+  Target &target = process->GetTarget();
+  Status error;
+  ModuleSpec module_spec;
+  module_spec.GetUUID() = uuid;
+
+  if (!uuid.IsValid() && !value_is_offset) {
+    memory_module_sp = ReadUnnamedMemoryModule(process, value);
+
+    if (memory_module_sp)
+      uuid = memory_module_sp->GetUUID();
+  }
+
+  if (uuid.IsValid()) {
+    ModuleSpec module_spec;
+    module_spec.GetUUID() = uuid;
+
+    if (!module_sp)
+      module_sp = target.GetOrCreateModule(module_spec, false, &error);
+
+    // If we haven't found a binary, or we don't have a SymbolFile, see
+    // if there is an external search tool that can find it.
+    if (force_symbol_search &&
+        (!module_sp || !module_sp->GetSymbolFileFileSpec())) {
+      Symbols::DownloadObjectAndSymbolFile(module_spec, error, true);
+      if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
+        module_sp = std::make_shared<Module>(module_spec);
+      }
+    }
+  }
+
+  // If we couldn't find the binary anywhere else, as a last resort,
+  // read it out of memory.
+  if (!module_sp.get() && value != LLDB_INVALID_ADDRESS && !value_is_offset) {
+    if (!memory_module_sp)
+      memory_module_sp = ReadUnnamedMemoryModule(process, value);
+    if (memory_module_sp)
+      module_sp = memory_module_sp;
+  }
+
+  Log *log = GetLog(LLDBLog::DynamicLoader);
+  if (module_sp.get()) {
+    target.GetImages().AppendIfNeeded(module_sp, false);
+
+    bool changed = false;
+    if (module_sp->GetObjectFile()) {
+      if (value != LLDB_INVALID_ADDRESS) {
+        LLDB_LOGF(log, "Loading binary UUID %s at %s 0x%" PRIx64,
+                  uuid.GetAsString().c_str(),
+                  value_is_offset ? "offset" : "address", value);
+        module_sp->SetLoadAddress(target, value, value_is_offset, changed);
+      } else {
+        // No address/offset/slide, load the binary at file address,
+        // offset 0.
+        LLDB_LOGF(log, "Loading binary UUID %s at file address",
+                  uuid.GetAsString().c_str());
+        module_sp->SetLoadAddress(target, 0, true /* value_is_slide */,
+                                  changed);
+      }
+    } else {
+      // In-memory image, load at its true address, offset 0.
+      LLDB_LOGF(log, "Loading binary UUID %s from memory at address 0x%" PRIx64,
+                uuid.GetAsString().c_str(), value);
+      module_sp->SetLoadAddress(target, 0, true /* value_is_slide */, changed);
+    }
+
+    if (notify) {
+      ModuleList added_module;
+      added_module.Append(module_sp, false);
+      target.ModulesDidLoad(added_module);
+    }
+  } else {
+    LLDB_LOGF(log, "Unable to find binary with UUID %s and load it at "
+                  "%s 0x%" PRIx64,
+                  uuid.GetAsString().c_str(),
+                  value_is_offset ? "offset" : "address", value);
+  }
+
+  return module_sp;
+}
+
 int64_t DynamicLoader::ReadUnsignedIntWithSizeInBytes(addr_t addr,
                                                       int size_in_bytes) {
   Status error;
diff --git a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 5dc71fc..15b06db 100644
--- a/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -5600,7 +5600,8 @@
             }
 
             if (m_data.CopyData(offset, sizeof(uuid_t), raw_uuid) != 0) {
-              uuid = UUID::fromOptionalData(raw_uuid, sizeof(uuid_t));
+              if (!uuid_is_null(raw_uuid))
+                uuid = UUID::fromOptionalData(raw_uuid, sizeof(uuid_t));
               // convert the "main bin spec" type into our
               // ObjectFile::BinaryType enum
               switch (binspec_type) {
@@ -6901,7 +6902,8 @@
 
           MachOCorefileImageEntry image_entry;
           image_entry.filename = (const char *)m_data.GetCStr(&filepath_offset);
-          image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
+          if (!uuid_is_null(uuid))
+            image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
           image_entry.load_address = load_address;
           image_entry.currently_executing = currently_executing;
 
@@ -6932,9 +6934,11 @@
 
           MachOCorefileImageEntry image_entry;
           image_entry.filename = filename;
-          image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
+          if (!uuid_is_null(uuid))
+            image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
           image_entry.load_address = load_address;
           image_entry.slide = slide;
+          image_entry.currently_executing = true;
           image_infos.all_image_infos.push_back(image_entry);
         }
       }
@@ -6951,42 +6955,41 @@
 
   ModuleList added_modules;
   for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) {
-    ModuleSpec module_spec;
-    module_spec.GetUUID() = image.uuid;
-    if (image.filename.empty()) {
-      char namebuf[80];
-      if (image.load_address != LLDB_INVALID_ADDRESS)
-        snprintf(namebuf, sizeof(namebuf), "mem-image-0x%" PRIx64,
-                 image.load_address);
-      else
-        snprintf(namebuf, sizeof(namebuf), "mem-image+0x%" PRIx64, image.slide);
-      module_spec.GetFileSpec() = FileSpec(namebuf);
-    } else {
-      module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
-    }
-    if (image.currently_executing) {
+    ModuleSP module_sp;
+
+    if (!image.filename.empty()) {
       Status error;
-      Symbols::DownloadObjectAndSymbolFile(module_spec, error, true);
-      if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
-        process.GetTarget().GetOrCreateModule(module_spec, false);
+      ModuleSpec module_spec;
+      module_spec.GetUUID() = image.uuid;
+      module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
+      if (image.currently_executing) {
+        Symbols::DownloadObjectAndSymbolFile(module_spec, error, 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()) {
+      module_sp =
+          process.GetTarget().GetOrCreateModule(module_spec, false, &error);
+      process.GetTarget().GetImages().AppendIfNeeded(module_sp,
+                                                     false /* notify */);
+    } else {
       if (image.load_address != LLDB_INVALID_ADDRESS) {
-        module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(),
-                                                 image.load_address);
+        module_sp = DynamicLoader::LoadBinaryWithUUIDAndAddress(
+            &process, image.uuid, image.load_address,
+            false /* value_is_offset */, image.currently_executing,
+            false /* notify */);
+      } else if (image.slide != LLDB_INVALID_ADDRESS) {
+        module_sp = DynamicLoader::LoadBinaryWithUUIDAndAddress(
+            &process, image.uuid, image.slide, true /* value_is_offset */,
+            image.currently_executing, false /* notify */);
       }
     }
+
     if (module_sp.get()) {
       // Will call ModulesDidLoad with all modules once they've all
       // been added to the Target with load addresses.  Don't notify
       // here, before the load address is set.
-      const bool notify = false;
-      process.GetTarget().GetImages().AppendIfNeeded(module_sp, notify);
-      added_modules.Append(module_sp, notify);
+      added_modules.Append(module_sp, false /* notify */);
       if (image.segment_load_addresses.size() > 0) {
         if (log) {
           std::string uuidstr = image.uuid.GetAsString();
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 580cdde..8687175 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -1033,6 +1033,13 @@
   return true;
 }
 
+std::vector<addr_t>
+GDBRemoteCommunicationClient::GetProcessStandaloneBinaries() {
+  if (m_qProcessInfo_is_valid == eLazyBoolCalculate)
+    GetCurrentProcessInfo();
+  return m_binary_addresses;
+}
+
 bool GDBRemoteCommunicationClient::GetGDBServerVersion() {
   if (m_qGDBServerVersion_is_valid == eLazyBoolCalculate) {
     m_gdb_server_name.clear();
@@ -2192,6 +2199,14 @@
             m_process_standalone_value_is_offset = false;
             ++num_keys_decoded;
           }
+        } else if (name.equals("binary-addresses")) {
+          addr_t addr;
+          while (!value.empty()) {
+            llvm::StringRef addr_str;
+            std::tie(addr_str, value) = value.split(',');
+            if (!addr_str.getAsInteger(16, addr))
+              m_binary_addresses.push_back(addr);
+          }
         }
       }
       if (num_keys_decoded > 0)
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 3d838d6..3a62747 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -220,6 +220,8 @@
   bool GetProcessStandaloneBinary(UUID &uuid, lldb::addr_t &value,
                                   bool &value_is_offset);
 
+  std::vector<lldb::addr_t> GetProcessStandaloneBinaries();
+
   void GetRemoteQSupported();
 
   bool GetVContSupported(char flavor);
@@ -593,6 +595,7 @@
   UUID m_process_standalone_uuid;
   lldb::addr_t m_process_standalone_value = LLDB_INVALID_ADDRESS;
   bool m_process_standalone_value_is_offset = false;
+  std::vector<lldb::addr_t> m_binary_addresses;
   llvm::VersionTuple m_os_version;
   llvm::VersionTuple m_maccatalyst_version;
   std::string m_os_build;
diff --git a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index b7de05d..e3dada1 100644
--- a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -581,80 +581,31 @@
         ModuleSP module_sp;
 
         if (standalone_uuid.IsValid()) {
-          ModuleSpec module_spec;
-          module_spec.GetUUID() = standalone_uuid;
+          const bool force_symbol_search = true;
+          const bool notify = true;
+          DynamicLoader::LoadBinaryWithUUIDAndAddress(
+              this, standalone_uuid, standalone_value,
+              standalone_value_is_offset, force_symbol_search, notify);
+        }
+      }
 
-          // Look up UUID in global module cache before attempting
-          // a more expensive search.
-          Status error = ModuleList::GetSharedModule(module_spec, module_sp,
-                                                     nullptr, nullptr, nullptr);
+      // The remote stub may know about a list of binaries to
+      // force load into the process -- a firmware type situation
+      // where multiple binaries are present in virtual memory,
+      // and we are only given the addresses of the binaries.
+      // Not intended for use with userland debugging when we
+      // a DynamicLoader plugin that knows how to find the loaded
+      // binaries and will track updates as binaries are added.
 
-          if (!module_sp) {
-            // Force a an external lookup, if that tool is available.
-            if (!module_spec.GetSymbolFileSpec()) {
-              Status error;
-              Symbols::DownloadObjectAndSymbolFile(module_spec, error, true);
-            }
-
-            if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
-              module_sp = std::make_shared<Module>(module_spec);
-            }
-          }
-
-          // If we couldn't find the binary anywhere else, as a last resort,
-          // read it out of memory.
-          if (!module_sp.get() && standalone_value != LLDB_INVALID_ADDRESS &&
-              !standalone_value_is_offset) {
-            char namebuf[80];
-            snprintf(namebuf, sizeof(namebuf), "mem-image-0x%" PRIx64,
-                     standalone_value);
-            module_sp =
-                ReadModuleFromMemory(FileSpec(namebuf), standalone_value);
-          }
-
-          Log *log = GetLog(LLDBLog::DynamicLoader);
-          if (module_sp.get()) {
-            target.GetImages().AppendIfNeeded(module_sp, false);
-
-            bool changed = false;
-            if (module_sp->GetObjectFile()) {
-              if (standalone_value != LLDB_INVALID_ADDRESS) {
-                if (log)
-                  log->Printf("Loading binary UUID %s at %s 0x%" PRIx64,
-                              standalone_uuid.GetAsString().c_str(),
-                              standalone_value_is_offset ? "offset" : "address",
-                              standalone_value);
-                module_sp->SetLoadAddress(target, standalone_value,
-                                          standalone_value_is_offset, changed);
-              } else {
-                // No address/offset/slide, load the binary at file address,
-                // offset 0.
-                if (log)
-                  log->Printf("Loading binary UUID %s at file address",
-                              standalone_uuid.GetAsString().c_str());
-                const bool value_is_slide = true;
-                module_sp->SetLoadAddress(target, 0, value_is_slide, changed);
-              }
-            } else {
-              // In-memory image, load at its true address, offset 0.
-              if (log)
-                log->Printf("Loading binary UUID %s from memory",
-                            standalone_uuid.GetAsString().c_str());
-              const bool value_is_slide = true;
-              module_sp->SetLoadAddress(target, 0, value_is_slide, changed);
-            }
-
-            ModuleList added_module;
-            added_module.Append(module_sp, false);
-            target.ModulesDidLoad(added_module);
-          } else {
-            if (log)
-              log->Printf("Unable to find binary with UUID %s and load it at "
-                          "%s 0x%" PRIx64,
-                          standalone_uuid.GetAsString().c_str(),
-                          standalone_value_is_offset ? "offset" : "address",
-                          standalone_value);
-          }
+      std::vector<addr_t> bin_addrs = m_gdb_comm.GetProcessStandaloneBinaries();
+      if (bin_addrs.size()) {
+        UUID uuid;
+        const bool value_is_slide = false;
+        for (addr_t addr : bin_addrs) {
+          const bool force_symbol_search = true;
+          const bool notify = true;
+          DynamicLoader::LoadBinaryWithUUIDAndAddress(
+              this, uuid, addr, value_is_slide, force_symbol_search, notify);
         }
       }
 
diff --git a/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/source/Plugins/Process/mach-core/ProcessMachCore.cpp
index 7f943a5..68786e8 100644
--- a/source/Plugins/Process/mach-core/ProcessMachCore.cpp
+++ b/source/Plugins/Process/mach-core/ProcessMachCore.cpp
@@ -180,88 +180,6 @@
   return false;
 }
 
-// We have a hint about a binary -- a UUID, possibly a load address.
-// Try to load a file with that UUID into lldb, and if we have a load
-// address, set it correctly.  Else assume that the binary was loaded
-// with no slide.
-static bool load_standalone_binary(UUID uuid, addr_t value,
-                                   bool value_is_offset, Target &target) {
-  if (uuid.IsValid()) {
-    ModuleSpec module_spec;
-    module_spec.GetUUID() = uuid;
-
-    // Look up UUID in global module cache before attempting
-    // dsymForUUID-like action.
-    ModuleSP module_sp;
-    Status error = ModuleList::GetSharedModule(module_spec, module_sp, nullptr,
-                                               nullptr, nullptr);
-
-    if (!module_sp.get()) {
-      // Force a a dsymForUUID lookup, if that tool is available.
-      if (!module_spec.GetSymbolFileSpec()) {
-        Status error;
-        Symbols::DownloadObjectAndSymbolFile(module_spec, error, true);
-      }
-
-      if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
-        module_sp = std::make_shared<Module>(module_spec);
-      }
-    }
-
-    // If we couldn't find the binary anywhere else, as a last resort,
-    // read it out of memory in the corefile.
-    if (!module_sp.get() && value != LLDB_INVALID_ADDRESS && !value_is_offset) {
-      char namebuf[80];
-      snprintf(namebuf, sizeof(namebuf), "mem-image-0x%" PRIx64, value);
-      module_sp =
-          target.GetProcessSP()->ReadModuleFromMemory(FileSpec(namebuf), value);
-    }
-
-    if (module_sp.get()) {
-      target.SetArchitecture(module_sp->GetObjectFile()->GetArchitecture());
-      target.GetImages().AppendIfNeeded(module_sp, false);
-
-      // TODO: Instead of using the load address as a value, if we create a
-      // memory module from that address, we could get the correct segment
-      // offset values from the in-memory load commands and set them correctly.
-      // In case the load address we were given is not correct for all segments,
-      // e.g. something in the shared cache.  DynamicLoaderDarwinKernel does
-      // something similar for kexts.  In the context of a corefile, this would
-      // be an inexpensive operation.  Not all binaries in a corefile will have
-      // a Mach-O header/load commands in memory, so this will not work in all
-      // cases.
-
-      bool changed = false;
-      if (module_sp->GetObjectFile()) {
-        if (value != LLDB_INVALID_ADDRESS) {
-          module_sp->SetLoadAddress(target, value, value_is_offset, changed);
-        } else {
-          // No address/offset/slide, load the binary at file address,
-          // offset 0.
-          const bool value_is_slide = true;
-          module_sp->SetLoadAddress(target, 0, value_is_slide, changed);
-        }
-      } else {
-        // In-memory image, load at its true address, offset 0.
-        const bool value_is_slide = true;
-        module_sp->SetLoadAddress(target, 0, value_is_slide, changed);
-      }
-
-      ModuleList added_module;
-      added_module.Append(module_sp, false);
-      target.ModulesDidLoad(added_module);
-
-      // Flush info in the process (stack frames, etc).
-      ProcessSP process_sp(target.GetProcessSP());
-      if (process_sp)
-        process_sp->Flush();
-
-      return true;
-    }
-  }
-  return false;
-}
-
 // Process Control
 Status ProcessMachCore::DoLoadCore() {
   Log *log(GetLog(LLDBLog::DynamicLoader | LLDBLog::Process));
@@ -359,28 +277,21 @@
           objfile_binary_uuid.GetAsString().c_str(), objfile_binary_value,
           objfile_binary_value_is_offset, type);
     }
-    if (objfile_binary_value != LLDB_INVALID_ADDRESS &&
-        !objfile_binary_value_is_offset) {
-      if (type == ObjectFile::eBinaryTypeUser) {
-        load_standalone_binary(objfile_binary_uuid, objfile_binary_value,
-                               objfile_binary_value_is_offset, GetTarget());
-        m_dyld_addr = objfile_binary_value;
-        m_dyld_plugin_name = DynamicLoaderMacOSXDYLD::GetPluginNameStatic();
-        found_main_binary_definitively = true;
-      }
-      if (type == ObjectFile::eBinaryTypeKernel) {
-        m_mach_kernel_addr = objfile_binary_value;
-        m_dyld_plugin_name = DynamicLoaderDarwinKernel::GetPluginNameStatic();
-        found_main_binary_definitively = true;
-      }
+    const bool force_symbol_search = true;
+    const bool notify = true;
+    if (DynamicLoader::LoadBinaryWithUUIDAndAddress(
+            this, objfile_binary_uuid, objfile_binary_value,
+            objfile_binary_value_is_offset, force_symbol_search, notify)) {
+      found_main_binary_definitively = true;
+      m_dyld_plugin_name = DynamicLoaderStatic::GetPluginNameStatic();
     }
-    if (!found_main_binary_definitively) {
-      // ObjectFile::eBinaryTypeStandalone, undeclared types
-      if (load_standalone_binary(objfile_binary_uuid, objfile_binary_value,
-                                 objfile_binary_value_is_offset, GetTarget())) {
-        found_main_binary_definitively = true;
-        m_dyld_plugin_name = DynamicLoaderStatic::GetPluginNameStatic();
-      }
+    if (type == ObjectFile::eBinaryTypeUser) {
+      m_dyld_addr = objfile_binary_value;
+      m_dyld_plugin_name = DynamicLoaderMacOSXDYLD::GetPluginNameStatic();
+    }
+    if (type == ObjectFile::eBinaryTypeKernel) {
+      m_mach_kernel_addr = objfile_binary_value;
+      m_dyld_plugin_name = DynamicLoaderDarwinKernel::GetPluginNameStatic();
     }
   }
 
@@ -426,8 +337,11 @@
       // We have no address specified, only a UUID.  Load it at the file
       // address.
       const bool value_is_offset = false;
-      if (load_standalone_binary(ident_uuid, ident_binary_addr, value_is_offset,
-                                 GetTarget())) {
+      const bool force_symbol_search = true;
+      const bool notify = true;
+      if (DynamicLoader::LoadBinaryWithUUIDAndAddress(
+              this, ident_uuid, ident_binary_addr, value_is_offset,
+              force_symbol_search, notify)) {
         found_main_binary_definitively = true;
         m_dyld_plugin_name = DynamicLoaderStatic::GetPluginNameStatic();
       }
diff --git a/source/Utility/UuidCompatibility.h b/source/Utility/UuidCompatibility.h
index e992c0c..fe4aed0 100644
--- a/source/Utility/UuidCompatibility.h
+++ b/source/Utility/UuidCompatibility.h
@@ -14,4 +14,12 @@
 // uuid_t is guaranteed to always be a 16-byte array
 typedef unsigned char uuid_t[16];
 
+// Return 1 if uuid is null, that is, all zeroes.
+int uuid_is_null(uuid_t uuid) {
+  for (int i = 0; i < 16; i++)
+    if (uuid[i])
+      return 0;
+  return 1;
+}
+
 #endif // utility_UUID_COMPATIBILITY_H
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/Makefile b/test/API/macosx/lc-note/multiple-binary-corefile/Makefile
new file mode 100644
index 0000000..8e561f1
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/Makefile
@@ -0,0 +1,20 @@
+MAKE_DSYM := NO
+C_SOURCES := main.c
+LD_EXTRAS := -L. -lone -ltwo
+
+.PHONY: libone.dylib libtwo.dylib
+all: libone.dylib libtwo.dylib a.out create-empty-corefile 
+
+create-empty-corefile:
+	"$(MAKE)" -f "$(MAKEFILE_RULES)" EXE=create-multibin-corefile \
+		CXX_SOURCES=create-multibin-corefile.cpp
+
+libone.dylib: one.c
+	$(MAKE) -f $(MAKEFILE_RULES) \
+		DYLIB_ONLY=YES DYLIB_NAME=one DYLIB_C_SOURCES=one.c
+
+libtwo.dylib: two.c
+	$(MAKE) -f $(MAKEFILE_RULES) \
+		DYLIB_ONLY=YES DYLIB_NAME=two DYLIB_C_SOURCES=two.c
+
+include Makefile.rules
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/TestMultipleBinaryCorefile.py b/test/API/macosx/lc-note/multiple-binary-corefile/TestMultipleBinaryCorefile.py
new file mode 100644
index 0000000..5e74f0c
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/TestMultipleBinaryCorefile.py
@@ -0,0 +1,187 @@
+"""Test corefiles with "main bin spec"/"load binary" with only addrs work."""
+
+
+import os
+import re
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestMultipleBinaryCorefile(TestBase):
+
+    def initial_setup(self):
+        self.build()
+        self.aout_exe_basename = "a.out"
+        self.libone_exe_basename = "libone.dylib"
+        self.libtwo_exe_basename = "libtwo.dylib"
+        self.aout_exe = self.getBuildArtifact(self.aout_exe_basename)
+        self.aout_slide = 0x5000
+        self.libone_exe = self.getBuildArtifact(self.libone_exe_basename)
+        self.libone_slide = 0x100840000
+        self.libtwo_exe = self.getBuildArtifact(self.libtwo_exe_basename)
+        self.libtwo_slide = 0
+        self.corefile = self.getBuildArtifact("multiple-binaries.core")
+        self.create_corefile = self.getBuildArtifact("create-multibin-corefile")
+        cmd="%s %s %s@%x %s@%x %s@%x" % (self.create_corefile, self.corefile, 
+                                self.aout_exe, self.aout_slide,
+                                self.libone_exe, self.libone_slide,
+                                self.libtwo_exe, self.libtwo_slide)
+        call(cmd, shell=True)
+
+
+    def load_corefile_and_test(self):
+        target = self.dbg.CreateTarget('')
+        err = lldb.SBError()
+        if self.TraceOn():
+            self.runCmd("script print('loading corefile %s')" % self.corefile)
+        process = target.LoadCore(self.corefile)
+        self.assertEqual(process.IsValid(), True)
+        if self.TraceOn():
+            self.runCmd("script print('image list after loading corefile:')")
+            self.runCmd("image list")
+
+        self.assertEqual(target.GetNumModules(), 3)
+        fspec = target.GetModuleAtIndex(0).GetFileSpec()
+        self.assertEqual(fspec.GetFilename(), self.aout_exe_basename)
+
+        # libone.dylib was never loaded into lldb, see that we added a memory module.
+        fspec = target.GetModuleAtIndex(1).GetFileSpec()
+        self.assertIn('memory-image', fspec.GetFilename())
+
+        dwarfdump_uuid_regex = re.compile(
+            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
+        dwarfdump_cmd_output = subprocess.check_output(
+                ('/usr/bin/dwarfdump --uuid "%s"' % self.libone_exe), shell=True).decode("utf-8")
+        libone_uuid = None
+        for line in dwarfdump_cmd_output.splitlines():
+            match = dwarfdump_uuid_regex.search(line)
+            if match:
+                libone_uuid = match.group(1)
+
+        memory_image_uuid = target.GetModuleAtIndex(1).GetUUIDString()
+        self.assertEqual(libone_uuid, memory_image_uuid)
+
+        fspec = target.GetModuleAtIndex(2).GetFileSpec()
+        self.assertEqual(fspec.GetFilename(), self.libtwo_exe_basename)
+
+        # Executables "always" have this base address
+        aout_load = target.GetModuleAtIndex(0).GetObjectFileHeaderAddress().GetLoadAddress(target)
+        self.assertEqual(aout_load, 0x100000000 + self.aout_slide)
+
+        # Value from Makefile
+        libone_load = target.GetModuleAtIndex(1).GetObjectFileHeaderAddress().GetLoadAddress(target)
+        self.assertEqual(libone_load, self.libone_slide)
+
+        # Value from Makefile
+        libtwo_load = target.GetModuleAtIndex(2).GetObjectFileHeaderAddress().GetLoadAddress(target)
+        self.assertEqual(libtwo_load, self.libtwo_slide)
+
+        self.dbg.DeleteTarget(target)
+        self.dbg.Clear()
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIf(archs=no_match(['x86_64', 'arm64', 'arm64e', 'aarch64']))
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_corefile_binaries_dsymforuuid(self):
+        self.initial_setup()
+
+        if self.TraceOn():
+            self.runCmd("log enable lldb dyld host")
+            self.addTearDownHook(lambda: self.runCmd("log disable lldb dyld host"))
+
+        ## We can hook in our dsym-for-uuid shell script to lldb with this env
+        ## var instead of requiring a defaults write.
+        dsym_for_uuid = self.getBuildArtifact("dsym-for-uuid.sh")
+        os.environ['LLDB_APPLE_DSYMFORUUID_EXECUTABLE'] = dsym_for_uuid
+        if self.TraceOn():
+            print("Setting env var LLDB_APPLE_DSYMFORUUID_EXECUTABLE=" + dsym_for_uuid)
+        self.addTearDownHook(lambda: os.environ.pop('LLDB_APPLE_DSYMFORUUID_EXECUTABLE', None))
+
+        self.runCmd("settings set target.load-script-from-symbol-file true")
+        self.addTearDownHook(lambda: self.runCmd("settings set target.load-script-from-symbol-file false"))
+
+        dwarfdump_uuid_regex = re.compile(
+            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
+        dwarfdump_cmd_output = subprocess.check_output(
+                ('/usr/bin/dwarfdump --uuid "%s"' % self.libtwo_exe), shell=True).decode("utf-8")
+        libtwo_uuid = None
+        for line in dwarfdump_cmd_output.splitlines():
+            match = dwarfdump_uuid_regex.search(line)
+            if match:
+                libtwo_uuid = match.group(1)
+        self.assertNotEqual(libtwo_uuid, None, "Could not get uuid of built libtwo.dylib")
+
+        ###  Create our dsym-for-uuid shell script which returns 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" ]' % (libtwo_uuid),
+                'then',
+                '  echo "<key>DBGError</key><string>not found</string>"',
+                '  echo "</plist>"', 
+                '  exit 1',
+                'fi',
+                '  uuid=%s' % libtwo_uuid,
+                '  bin=%s' % self.libtwo_exe,
+                '  dsym=%s.dSYM/Contents/Resources/DWARF/%s' % (self.libtwo_exe, os.path.basename(self.libtwo_exe)),
+                'echo "<dict><key>$uuid</key><dict>"',
+                '',
+                'echo "<key>DBGDSYMPath</key><string>$dsym</string>"',
+                'echo "<key>DBGSymbolRichExecutable</key><string>$bin</string>"',
+                'echo "</dict></dict></plist>"',
+                'exit $ret'
+                ]
+
+        with open(dsym_for_uuid, "w") as writer:
+            for l in shell_cmds:
+                writer.write(l + '\n')
+
+        os.chmod(dsym_for_uuid, 0o755)
+
+        # Register TWO of our binaries, but require dsymForUUID to find the third.
+        target = self.dbg.CreateTarget(self.aout_exe, '', '', False, lldb.SBError())
+        self.dbg.DeleteTarget(target)
+
+        if self.TraceOn():
+            self.runCmd("script print('Global image list, before loading corefile:')")
+            self.runCmd("image list -g")
+
+        self.load_corefile_and_test()
+
+    @skipIf(archs=no_match(['x86_64', 'arm64', 'arm64e', 'aarch64']))
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_corefile_binaries_preloaded(self):
+        self.initial_setup()
+
+        if self.TraceOn():
+            self.runCmd("log enable lldb dyld host")
+            self.addTearDownHook(lambda: self.runCmd("log disable lldb dyld host"))
+
+        # Register all three binaries in lldb's global module
+        # cache, then throw the Targets away.
+        target = self.dbg.CreateTarget(self.aout_exe, '', '', False, lldb.SBError())
+        self.dbg.DeleteTarget(target)
+        target = self.dbg.CreateTarget(self.libtwo_exe, '', '', False, lldb.SBError())
+        self.dbg.DeleteTarget(target)
+
+        if self.TraceOn():
+            self.runCmd("script print('Global image list, before loading corefile:')")
+            self.runCmd("image list -g")
+
+        self.load_corefile_and_test()
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/create-multibin-corefile.cpp b/test/API/macosx/lc-note/multiple-binary-corefile/create-multibin-corefile.cpp
new file mode 100644
index 0000000..ebe7160
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/create-multibin-corefile.cpp
@@ -0,0 +1,484 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <mach-o/loader.h>
+#include <mach/thread_status.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <vector>
+
+// Given a list of binaries, and optional slides to be applied,
+// create a corefile whose memory is those binaries laid at at
+// their slid addresses.
+//
+// Add a 'main bin spec' LC_NOTE for the first binary, and
+// 'load binary' LC_NOTEs for any additional binaries, and
+// these LC_NOTEs will ONLY have the vmaddr of the binary - no
+// UUID, no slide, no filename.
+//
+// Test that lldb can use the load addresses, find the UUIDs,
+// and load the binaries/dSYMs and put them at the correct load
+// address.
+
+struct main_bin_spec_payload {
+  uint32_t version;
+  uint32_t type;
+  uint64_t address;
+  uint64_t slide;
+  uuid_t uuid;
+  uint32_t log2_pagesize;
+  uint32_t platform;
+};
+
+struct load_binary_payload {
+  uint32_t version;
+  uuid_t uuid;
+  uint64_t address;
+  uint64_t slide;
+  const char name[4];
+};
+
+union uint32_buf {
+  uint8_t bytebuf[4];
+  uint32_t val;
+};
+
+union uint64_buf {
+  uint8_t bytebuf[8];
+  uint64_t val;
+};
+
+void add_uint64(std::vector<uint8_t> &buf, uint64_t val) {
+  uint64_buf conv;
+  conv.val = val;
+  for (int i = 0; i < 8; i++)
+    buf.push_back(conv.bytebuf[i]);
+}
+
+void add_uint32(std::vector<uint8_t> &buf, uint32_t val) {
+  uint32_buf conv;
+  conv.val = val;
+  for (int i = 0; i < 4; i++)
+    buf.push_back(conv.bytebuf[i]);
+}
+
+std::vector<uint8_t> lc_thread_load_command(cpu_type_t cputype) {
+  std::vector<uint8_t> data;
+  // Emit an LC_THREAD register context appropriate for the cputype
+  // of the binary we're embedded.  The tests in this case do not
+  // use the register values, so 0's are fine, lldb needs to see at
+  // least one LC_THREAD in the corefile.
+#if defined(__x86_64__)
+  if (cputype == CPU_TYPE_X86_64) {
+    add_uint32(data, LC_THREAD); // thread_command.cmd
+    add_uint32(data,
+               16 + (x86_THREAD_STATE64_COUNT * 4)); // thread_command.cmdsize
+    add_uint32(data, x86_THREAD_STATE64);            // thread_command.flavor
+    add_uint32(data, x86_THREAD_STATE64_COUNT);      // thread_command.count
+    for (int i = 0; i < x86_THREAD_STATE64_COUNT; i++) {
+      add_uint32(data, 0); // whatever, just some empty register values
+    }
+  }
+#endif
+#if defined(__arm64__) || defined(__aarch64__)
+  if (cputype == CPU_TYPE_ARM64) {
+    add_uint32(data, LC_THREAD); // thread_command.cmd
+    add_uint32(data,
+               16 + (ARM_THREAD_STATE64_COUNT * 4)); // thread_command.cmdsize
+    add_uint32(data, ARM_THREAD_STATE64);            // thread_command.flavor
+    add_uint32(data, ARM_THREAD_STATE64_COUNT);      // thread_command.count
+    for (int i = 0; i < ARM_THREAD_STATE64_COUNT; i++) {
+      add_uint32(data, 0); // whatever, just some empty register values
+    }
+  }
+#endif
+  return data;
+}
+
+void add_lc_note_main_bin_spec_load_command(
+    std::vector<std::vector<uint8_t>> &loadcmds, std::vector<uint8_t> &payload,
+    int payload_file_offset, std::string uuidstr, uint64_t address,
+    uint64_t slide) {
+  std::vector<uint8_t> loadcmd_data;
+
+  add_uint32(loadcmd_data, LC_NOTE); // note_command.cmd
+  add_uint32(loadcmd_data, 40);      // note_command.cmdsize
+  char lc_note_name[16];
+  memset(lc_note_name, 0, 16);
+  strcpy(lc_note_name, "main bin spec");
+
+  // lc_note.data_owner
+  for (int i = 0; i < 16; i++)
+    loadcmd_data.push_back(lc_note_name[i]);
+
+  // we start writing the payload at payload_file_offset to leave
+  // room at the start for the header & the load commands.
+  uint64_t current_payload_offset = payload.size() + payload_file_offset;
+
+  add_uint64(loadcmd_data, current_payload_offset); // note_command.offset
+  add_uint64(loadcmd_data,
+             sizeof(struct main_bin_spec_payload)); // note_command.size
+
+  loadcmds.push_back(loadcmd_data);
+
+  // Now write the "main bin spec" payload.
+  add_uint32(payload, 2);       // version
+  add_uint32(payload, 3);       // type == 3 [ firmware, standalone, etc ]
+  add_uint64(payload, address); // load address
+  add_uint64(payload, slide);   // slide
+  uuid_t uuid;
+  uuid_parse(uuidstr.c_str(), uuid);
+  for (int i = 0; i < sizeof(uuid_t); i++)
+    payload.push_back(uuid[i]);
+  add_uint32(payload, 0); // log2_pagesize unspecified
+  add_uint32(payload, 0); // platform unspecified
+}
+
+void add_lc_note_load_binary_load_command(
+    std::vector<std::vector<uint8_t>> &loadcmds, std::vector<uint8_t> &payload,
+    int payload_file_offset, std::string uuidstr, uint64_t address,
+    uint64_t slide) {
+  std::vector<uint8_t> loadcmd_data;
+
+  add_uint32(loadcmd_data, LC_NOTE); // note_command.cmd
+  add_uint32(loadcmd_data, 40);      // note_command.cmdsize
+  char lc_note_name[16];
+  memset(lc_note_name, 0, 16);
+  strcpy(lc_note_name, "load binary");
+
+  // lc_note.data_owner
+  for (int i = 0; i < 16; i++)
+    loadcmd_data.push_back(lc_note_name[i]);
+
+  // we start writing the payload at payload_file_offset to leave
+  // room at the start for the header & the load commands.
+  uint64_t current_payload_offset = payload.size() + payload_file_offset;
+
+  add_uint64(loadcmd_data, current_payload_offset); // note_command.offset
+  add_uint64(loadcmd_data,
+             sizeof(struct load_binary_payload)); // note_command.size
+
+  loadcmds.push_back(loadcmd_data);
+
+  // Now write the "load binary" payload.
+  add_uint32(payload, 1); // version
+  uuid_t uuid;
+  uuid_parse(uuidstr.c_str(), uuid);
+  for (int i = 0; i < sizeof(uuid_t); i++)
+    payload.push_back(uuid[i]);
+  add_uint64(payload, address); // load address
+  add_uint64(payload, slide);   // slide
+  add_uint32(payload, 0);       // name
+}
+
+void add_lc_segment(std::vector<std::vector<uint8_t>> &loadcmds,
+                    std::vector<uint8_t> &payload, int payload_file_offset,
+                    uint64_t vmaddr, uint64_t size) {
+  std::vector<uint8_t> loadcmd_data;
+  struct segment_command_64 seg;
+  seg.cmd = LC_SEGMENT_64;
+  seg.cmdsize = sizeof(struct segment_command_64); // no sections
+  memset(seg.segname, 0, 16);
+  seg.vmaddr = vmaddr;
+  seg.vmsize = size;
+  seg.fileoff = payload.size() + payload_file_offset;
+  seg.filesize = size;
+  seg.maxprot = 1;
+  seg.initprot = 1;
+  seg.nsects = 0;
+  seg.flags = 0;
+
+  uint8_t *p = (uint8_t *)&seg;
+  for (int i = 0; i < sizeof(struct segment_command_64); i++) {
+    loadcmd_data.push_back(*(p + i));
+  }
+  loadcmds.push_back(loadcmd_data);
+}
+
+std::string scan_binary(const char *fn, uint64_t &vmaddr, cpu_type_t &cputype,
+                        cpu_subtype_t &cpusubtype) {
+  FILE *f = fopen(fn, "r");
+  if (f == nullptr) {
+    fprintf(stderr, "Unable to open binary '%s' to get uuid\n", fn);
+    exit(1);
+  }
+  uint32_t num_of_load_cmds = 0;
+  uint32_t size_of_load_cmds = 0;
+  std::string uuid;
+  off_t file_offset = 0;
+  vmaddr = UINT64_MAX;
+
+  uint8_t magic[4];
+  if (::fread(magic, 1, 4, f) != 4) {
+    fprintf(stderr, "Failed to read magic number from input file %s\n", fn);
+    exit(1);
+  }
+  uint8_t magic_32_be[] = {0xfe, 0xed, 0xfa, 0xce};
+  uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe};
+  uint8_t magic_64_be[] = {0xfe, 0xed, 0xfa, 0xcf};
+  uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe};
+
+  if (memcmp(magic, magic_32_be, 4) == 0 ||
+      memcmp(magic, magic_64_be, 4) == 0) {
+    fprintf(stderr, "big endian corefiles not supported\n");
+    exit(1);
+  }
+
+  ::fseeko(f, 0, SEEK_SET);
+  if (memcmp(magic, magic_32_le, 4) == 0) {
+    struct mach_header mh;
+    if (::fread(&mh, 1, sizeof(mh), f) != sizeof(mh)) {
+      fprintf(stderr, "error reading mach header from input file\n");
+      exit(1);
+    }
+    if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) {
+      fprintf(stderr,
+              "This tool creates an x86_64/arm64 corefile but "
+              "the supplied binary '%s' is cputype 0x%x\n",
+              fn, (uint32_t)mh.cputype);
+      exit(1);
+    }
+    num_of_load_cmds = mh.ncmds;
+    size_of_load_cmds = mh.sizeofcmds;
+    file_offset += sizeof(struct mach_header);
+    cputype = mh.cputype;
+    cpusubtype = mh.cpusubtype;
+  } else {
+    struct mach_header_64 mh;
+    if (::fread(&mh, 1, sizeof(mh), f) != sizeof(mh)) {
+      fprintf(stderr, "error reading mach header from input file\n");
+      exit(1);
+    }
+    if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) {
+      fprintf(stderr,
+              "This tool creates an x86_64/arm64 corefile but "
+              "the supplied binary '%s' is cputype 0x%x\n",
+              fn, (uint32_t)mh.cputype);
+      exit(1);
+    }
+    num_of_load_cmds = mh.ncmds;
+    size_of_load_cmds = mh.sizeofcmds;
+    file_offset += sizeof(struct mach_header_64);
+    cputype = mh.cputype;
+    cpusubtype = mh.cpusubtype;
+  }
+
+  off_t load_cmds_offset = file_offset;
+
+  for (int i = 0; i < num_of_load_cmds &&
+                  (file_offset - load_cmds_offset) < size_of_load_cmds;
+       i++) {
+    ::fseeko(f, file_offset, SEEK_SET);
+    uint32_t cmd;
+    uint32_t cmdsize;
+    ::fread(&cmd, sizeof(uint32_t), 1, f);
+    ::fread(&cmdsize, sizeof(uint32_t), 1, f);
+    if (vmaddr == UINT64_MAX && cmd == LC_SEGMENT_64) {
+      struct segment_command_64 segcmd;
+      ::fseeko(f, file_offset, SEEK_SET);
+      if (::fread(&segcmd, 1, sizeof(segcmd), f) != sizeof(segcmd)) {
+        fprintf(stderr, "Unable to read LC_SEGMENT_64 load command.\n");
+        exit(1);
+      }
+      if (strcmp("__TEXT", segcmd.segname) == 0)
+        vmaddr = segcmd.vmaddr;
+    }
+    if (cmd == LC_UUID) {
+      struct uuid_command uuidcmd;
+      ::fseeko(f, file_offset, SEEK_SET);
+      if (::fread(&uuidcmd, 1, sizeof(uuidcmd), f) != sizeof(uuidcmd)) {
+        fprintf(stderr, "Unable to read LC_UUID load command.\n");
+        exit(1);
+      }
+      uuid_string_t uuidstr;
+      uuid_unparse(uuidcmd.uuid, uuidstr);
+      uuid = uuidstr;
+    }
+    file_offset += cmdsize;
+  }
+  return uuid;
+}
+
+void slide_macho_binary(std::vector<uint8_t> &image, uint64_t slide) {
+  uint8_t *p = image.data();
+  struct mach_header_64 *mh = (struct mach_header_64 *)p;
+  p += sizeof(struct mach_header_64);
+  for (int lc_idx = 0; lc_idx < mh->ncmds; lc_idx++) {
+    struct load_command *lc = (struct load_command *)p;
+    if (lc->cmd == LC_SEGMENT_64) {
+      struct segment_command_64 *seg = (struct segment_command_64 *)p;
+      if (seg->maxprot != 0 && seg->nsects > 0) {
+        seg->vmaddr += slide;
+        uint8_t *j = p + sizeof(segment_command_64);
+        for (int sect_idx = 0; sect_idx < seg->nsects; sect_idx++) {
+          struct section_64 *sect = (struct section_64 *)j;
+          sect->addr += slide;
+          j += sizeof(struct section_64);
+        }
+      }
+    }
+    p += lc->cmdsize;
+  }
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    fprintf(stderr,
+            "usage: output-corefile binary1[@optional-slide] "
+            "[binary2[@optional-slide] [binary3[@optional-slide] ...]]\n");
+    exit(1);
+  }
+
+  // An array of load commands (in the form of byte arrays)
+  std::vector<std::vector<uint8_t>> load_commands;
+
+  // An array of corefile contents (page data, lc_note data, etc)
+  std::vector<uint8_t> payload;
+
+  std::vector<std::string> input_filenames;
+  std::vector<uint64_t> input_slides;
+  std::vector<uint64_t> input_filesizes;
+  std::vector<uint64_t> input_filevmaddrs;
+  uint64_t main_binary_cputype = CPU_TYPE_ARM64;
+  uint64_t vmaddr = UINT64_MAX;
+  cpu_type_t cputype;
+  cpu_subtype_t cpusubtype;
+  for (int i = 2; i < argc; i++) {
+    std::string filename;
+    std::string filename_and_opt_hex(argv[i]);
+    uint64_t slide = 0;
+    auto at_pos = filename_and_opt_hex.find_last_of('@');
+    if (at_pos == std::string::npos) {
+      filename = filename_and_opt_hex;
+    } else {
+      filename = filename_and_opt_hex.substr(0, at_pos);
+      std::string hexstr = filename_and_opt_hex.substr(at_pos + 1);
+      errno = 0;
+      slide = (uint64_t)strtoull(hexstr.c_str(), nullptr, 16);
+      if (errno != 0) {
+        fprintf(stderr, "Unable to parse hex slide value in %s\n", argv[i]);
+        exit(1);
+      }
+    }
+    struct stat stbuf;
+    if (stat(filename.c_str(), &stbuf) == -1) {
+      fprintf(stderr, "Unable to stat '%s', exiting.\n", filename.c_str());
+      exit(1);
+    }
+    input_filenames.push_back(filename);
+    input_slides.push_back(slide);
+    input_filesizes.push_back(stbuf.st_size);
+    scan_binary(filename.c_str(), vmaddr, cputype, cpusubtype);
+    input_filevmaddrs.push_back(vmaddr + slide);
+    if (i == 2) {
+      main_binary_cputype = cputype;
+    }
+  }
+
+  const char *output_corefile_name = argv[1];
+  std::string empty_uuidstr = "00000000-0000-0000-0000-000000000000";
+
+  // First add all the load commands / payload so we can figure out how large
+  // the load commands will actually be.
+  load_commands.push_back(lc_thread_load_command(cputype));
+
+  add_lc_note_main_bin_spec_load_command(load_commands, payload, 0,
+                                         empty_uuidstr, 0, UINT64_MAX);
+  for (int i = 1; i < input_filenames.size(); i++) {
+    add_lc_note_load_binary_load_command(load_commands, payload, 0,
+                                         empty_uuidstr, 0, UINT64_MAX);
+  }
+
+  for (int i = 0; i < input_filenames.size(); i++) {
+    add_lc_segment(load_commands, payload, 0, 0, 0);
+  }
+
+  int size_of_load_commands = 0;
+  for (const auto &lc : load_commands)
+    size_of_load_commands += lc.size();
+
+  int size_of_header_and_load_cmds =
+      sizeof(struct mach_header_64) + size_of_load_commands;
+
+  // Erase the load commands / payload now that we know how much space is
+  // needed, redo it.
+  load_commands.clear();
+  payload.clear();
+
+  // Push the LC_THREAD load command.
+  load_commands.push_back(lc_thread_load_command(main_binary_cputype));
+
+  const off_t payload_offset = size_of_header_and_load_cmds;
+
+  add_lc_note_main_bin_spec_load_command(load_commands, payload, payload_offset,
+                                         empty_uuidstr, input_filevmaddrs[0],
+                                         UINT64_MAX);
+
+  for (int i = 1; i < input_filenames.size(); i++) {
+    add_lc_note_load_binary_load_command(load_commands, payload, payload_offset,
+                                         empty_uuidstr, input_filevmaddrs[i],
+                                         UINT64_MAX);
+  }
+
+  for (int i = 0; i < input_filenames.size(); i++) {
+    add_lc_segment(load_commands, payload, payload_offset, input_filevmaddrs[i],
+                   input_filesizes[i]);
+
+    // Copy the contents of the binary into payload.
+    int fd = open(input_filenames[i].c_str(), O_RDONLY);
+    if (fd == -1) {
+      fprintf(stderr, "Unable to open %s for reading\n",
+              input_filenames[i].c_str());
+      exit(1);
+    }
+    std::vector<uint8_t> binary_contents;
+    for (int j = 0; j < input_filesizes[i]; j++) {
+      uint8_t byte;
+      read(fd, &byte, 1);
+      binary_contents.push_back(byte);
+    }
+    close(fd);
+
+    size_t cur_payload_size = payload.size();
+    payload.resize(cur_payload_size + binary_contents.size());
+    slide_macho_binary(binary_contents, input_slides[i]);
+    memcpy(payload.data() + cur_payload_size, binary_contents.data(),
+           binary_contents.size());
+  }
+
+  struct mach_header_64 mh;
+  mh.magic = MH_MAGIC_64;
+  mh.cputype = cputype;
+
+  mh.cpusubtype = cpusubtype;
+  mh.filetype = MH_CORE;
+  mh.ncmds = load_commands.size();
+  mh.sizeofcmds = size_of_load_commands;
+  mh.flags = 0;
+  mh.reserved = 0;
+
+  FILE *f = fopen(output_corefile_name, "w");
+
+  if (f == nullptr) {
+    fprintf(stderr, "Unable to open file %s for writing\n",
+            output_corefile_name);
+    exit(1);
+  }
+
+  fwrite(&mh, sizeof(mh), 1, f);
+
+  for (const auto &lc : load_commands)
+    fwrite(lc.data(), lc.size(), 1, f);
+
+  fwrite(payload.data(), payload.size(), 1, f);
+
+  fclose(f);
+}
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/main.c b/test/API/macosx/lc-note/multiple-binary-corefile/main.c
new file mode 100644
index 0000000..eaab873
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/main.c
@@ -0,0 +1,7 @@
+#include <stdio.h>
+int one();
+int two();
+int main() {
+  puts("this is the standalone binary test program");
+  return one() + two();
+}
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/one.c b/test/API/macosx/lc-note/multiple-binary-corefile/one.c
new file mode 100644
index 0000000..6e8fe4a
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/one.c
@@ -0,0 +1 @@
+int one() { return 5; }
diff --git a/test/API/macosx/lc-note/multiple-binary-corefile/two.c b/test/API/macosx/lc-note/multiple-binary-corefile/two.c
new file mode 100644
index 0000000..f44baa6
--- /dev/null
+++ b/test/API/macosx/lc-note/multiple-binary-corefile/two.c
@@ -0,0 +1 @@
+int two() { return 10; }