Re-land: [lldb] Use vFlash commands when writing to target's flash memory regions

The difference between this and the previous patch is that now we use
ELF physical addresses only for loading objects into the target (and the
rest of the module load address logic still uses virtual addresses).

Summary:
When writing an object file over gdb-remote, use the vFlashErase, vFlashWrite, and vFlashDone commands if the write address is in a flash memory region.  A bare metal target may have this kind of setup.

- Update ObjectFileELF to set load addresses using physical addresses.  A typical case may be a data section with a physical address in ROM and a virtual address in RAM, which should be loaded to the ROM address.
- Add support for querying the target's qXfer:memory-map, which contains information about flash memory regions, leveraging MemoryRegionInfo data structures with minor modifications
- Update ProcessGDBRemote to use vFlash commands in DoWriteMemory when the target address is in a flash region

Original discussion at http://lists.llvm.org/pipermail/lldb-dev/2018-January/013093.html

Reviewers: clayborg, labath

Reviewed By: labath

Subscribers: llvm-commits, arichardson, emaste, mgorny, lldb-commits

Differential Revision: https://reviews.llvm.org/D42145
Patch by Owen Shaw <llvm@owenpshaw.net>.

git-svn-id: https://llvm.org/svn/llvm-project/lldb/trunk@327970 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/lldb/Core/Module.h b/include/lldb/Core/Module.h
index da981ad..8ea024c 100644
--- a/include/lldb/Core/Module.h
+++ b/include/lldb/Core/Module.h
@@ -1021,20 +1021,6 @@
   bool RemapSourceFile(llvm::StringRef path, std::string &new_path) const;
   bool RemapSourceFile(const char *, std::string &) const = delete;
 
-  //------------------------------------------------------------------
-  /// Loads this module to memory.
-  ///
-  /// Loads the bits needed to create an executable image to the memory.
-  /// It is useful with bare-metal targets where target does not have the
-  /// ability to start a process itself.
-  ///
-  /// @param[in] target
-  ///     Target where to load the module.
-  ///
-  /// @return
-  //------------------------------------------------------------------
-  Status LoadInMemory(Target &target, bool set_pc);
-
   //----------------------------------------------------------------------
   /// @class LookupInfo Module.h "lldb/Core/Module.h"
   /// @brief A class that encapsulates name lookup information.
diff --git a/include/lldb/Host/XML.h b/include/lldb/Host/XML.h
index 96b5227..c274e83 100644
--- a/include/lldb/Host/XML.h
+++ b/include/lldb/Host/XML.h
@@ -82,6 +82,9 @@
   llvm::StringRef GetAttributeValue(const char *name,
                                     const char *fail_value = nullptr) const;
 
+  bool GetAttributeValueAsUnsigned(const char *name, uint64_t &value,
+                                   uint64_t fail_value = 0, int base = 0) const;
+
   XMLNode FindFirstChildElementWithName(const char *name) const;
 
   XMLNode GetElementForPath(const NamePath &path);
diff --git a/include/lldb/Symbol/ObjectFile.h b/include/lldb/Symbol/ObjectFile.h
index dff8025..d96f32c 100644
--- a/include/lldb/Symbol/ObjectFile.h
+++ b/include/lldb/Symbol/ObjectFile.h
@@ -88,6 +88,11 @@
     eStrataJIT
   } Strata;
 
+  struct LoadableData {
+    lldb::addr_t Dest;
+    llvm::ArrayRef<uint8_t> Contents;
+  };
+
   //------------------------------------------------------------------
   /// Construct with a parent module, offset, and header data.
   ///
@@ -838,7 +843,7 @@
   ///
   /// @return
   //------------------------------------------------------------------
-  virtual Status LoadInMemory(Target &target, bool set_pc);
+  virtual std::vector<LoadableData> GetLoadableData(Target &target);
 
 protected:
   //------------------------------------------------------------------
diff --git a/include/lldb/Target/MemoryRegionInfo.h b/include/lldb/Target/MemoryRegionInfo.h
index 0824b24..63a7b72 100644
--- a/include/lldb/Target/MemoryRegionInfo.h
+++ b/include/lldb/Target/MemoryRegionInfo.h
@@ -25,7 +25,7 @@
 
   MemoryRegionInfo()
       : m_range(), m_read(eDontKnow), m_write(eDontKnow), m_execute(eDontKnow),
-        m_mapped(eDontKnow) {}
+        m_mapped(eDontKnow), m_flash(eDontKnow), m_blocksize(0) {}
 
   ~MemoryRegionInfo() {}
 
@@ -58,6 +58,14 @@
 
   void SetName(const char *name) { m_name = ConstString(name); }
 
+  OptionalBool GetFlash() const { return m_flash; }
+
+  void SetFlash(OptionalBool val) { m_flash = val; }
+
+  lldb::offset_t GetBlocksize() const { return m_blocksize; }
+
+  void SetBlocksize(lldb::offset_t blocksize) { m_blocksize = blocksize; }
+
   //----------------------------------------------------------------------
   // Get permissions as a uint32_t that is a mask of one or more bits from
   // the lldb::Permissions
@@ -98,6 +106,8 @@
   OptionalBool m_execute;
   OptionalBool m_mapped;
   ConstString m_name;
+  OptionalBool m_flash;
+  lldb::offset_t m_blocksize;
 };
 }
 
diff --git a/include/lldb/Target/Process.h b/include/lldb/Target/Process.h
index 16f37f4..cd897e7 100644
--- a/include/lldb/Target/Process.h
+++ b/include/lldb/Target/Process.h
@@ -38,6 +38,7 @@
 #include "lldb/Host/HostThread.h"
 #include "lldb/Host/ProcessRunLock.h"
 #include "lldb/Interpreter/Options.h"
+#include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Target/ExecutionContextScope.h"
 #include "lldb/Target/InstrumentationRuntime.h"
 #include "lldb/Target/Memory.h"
@@ -1950,6 +1951,8 @@
     return LLDB_INVALID_ADDRESS;
   }
 
+  virtual Status WriteObjectFile(std::vector<ObjectFile::LoadableData> entries);
+
   //------------------------------------------------------------------
   /// The public interface to allocating memory in the process.
   ///
diff --git a/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteLoad.py b/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteLoad.py
new file mode 100644
index 0000000..cce19a3
--- /dev/null
+++ b/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteLoad.py
@@ -0,0 +1,61 @@
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from gdbclientutils import *
+
+
+class TestGDBRemoteLoad(GDBRemoteTestBase):
+
+    def test_ram_load(self):
+        """Test loading an object file to a target's ram"""
+        target = self.createTarget("a.yaml")
+        process = self.connect(target)
+        self.dbg.HandleCommand("target modules load -l -s0")
+        self.assertPacketLogContains([
+                "M1000,4:c3c3c3c3",
+                "M1004,2:3232"
+                ])
+
+    @skipIfXmlSupportMissing
+    def test_flash_load(self):
+        """Test loading an object file to a target's flash memory"""
+
+        class Responder(MockGDBServerResponder):
+            def qSupported(self, client_supported):
+                return "PacketSize=3fff;QStartNoAckMode+;qXfer:memory-map:read+"
+
+            def qXferRead(self, obj, annex, offset, length):
+                if obj == "memory-map":
+                    return (self.MEMORY_MAP[offset:offset + length],
+                            offset + length < len(self.MEMORY_MAP))
+                return None, False
+
+            def other(self, packet):
+                if packet[0:11] == "vFlashErase":
+                    return "OK"
+                if packet[0:11] == "vFlashWrite":
+                    return "OK"
+                if packet == "vFlashDone":
+                    return "OK"
+                return ""
+
+            MEMORY_MAP = """<?xml version="1.0"?>
+<memory-map>
+  <memory type="ram" start="0x0" length="0x1000"/>
+  <memory type="flash" start="0x1000" length="0x1000">
+    <property name="blocksize">0x100</property>
+  </memory>
+  <memory type="ram" start="0x2000" length="0x1D400"/>
+</memory-map>
+"""
+
+        self.server.responder = Responder()
+        target = self.createTarget("a.yaml")
+        process = self.connect(target)
+        self.dbg.HandleCommand("target modules load -l -s0")
+        self.assertPacketLogContains([
+                "vFlashErase:1000,100",
+                "vFlashWrite:1000:\xc3\xc3\xc3\xc3",
+                "vFlashWrite:1004:\x32\x32",
+                "vFlashDone"
+                ])
diff --git a/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py b/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py
index e324229..d3c9f32 100644
--- a/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py
+++ b/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py
@@ -453,5 +453,5 @@
                 i += 1
             j += 1
         if i < len(packets):
-            self.fail("Did not receive: %s\nLast 10 packets:\n\t%s" %
-                    (packets[i], '\n\t'.join(log[-10:])))
+            self.fail(u"Did not receive: %s\nLast 10 packets:\n\t%s" %
+                    (packets[i], u'\n\t'.join(log[-10:])))
diff --git a/source/Commands/CommandObjectTarget.cpp b/source/Commands/CommandObjectTarget.cpp
index c83061d..7fb9c43 100644
--- a/source/Commands/CommandObjectTarget.cpp
+++ b/source/Commands/CommandObjectTarget.cpp
@@ -45,6 +45,7 @@
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/ABI.h"
 #include "lldb/Target/Process.h"
+#include "lldb/Target/RegisterContext.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/StackFrame.h"
 #include "lldb/Target/Thread.h"
@@ -2746,11 +2747,34 @@
                     process->Flush();
                 }
                 if (load) {
-                  Status error = module->LoadInMemory(*target, set_pc);
+                  ProcessSP process = target->CalculateProcess();
+                  Address file_entry = objfile->GetEntryPointAddress();
+                  if (!process) {
+                    result.AppendError("No process");
+                    return false;
+                  }
+                  if (set_pc && !file_entry.IsValid()) {
+                    result.AppendError("No entry address in object file");
+                    return false;
+                  }
+                  std::vector<ObjectFile::LoadableData> loadables(
+                      objfile->GetLoadableData(*target));
+                  if (loadables.size() == 0) {
+                    result.AppendError("No loadable sections");
+                    return false;
+                  }
+                  Status error = process->WriteObjectFile(std::move(loadables));
                   if (error.Fail()) {
                     result.AppendError(error.AsCString());
                     return false;
                   }
+                  if (set_pc) {
+                    ThreadList &thread_list = process->GetThreadList();
+                    ThreadSP curr_thread(thread_list.GetSelectedThread());
+                    RegisterContextSP reg_context(
+                        curr_thread->GetRegisterContext());
+                    reg_context->SetPC(file_entry.GetLoadAddress(target));
+                  }
                 }
               } else {
                 module->GetFileSpec().GetPath(path, sizeof(path));
diff --git a/source/Core/Module.cpp b/source/Core/Module.cpp
index 8f80be4..39d7c62 100644
--- a/source/Core/Module.cpp
+++ b/source/Core/Module.cpp
@@ -1685,7 +1685,3 @@
 
   return false;
 }
-
-Status Module::LoadInMemory(Target &target, bool set_pc) {
-  return m_objfile_sp->LoadInMemory(target, set_pc);
-}
diff --git a/source/Host/common/XML.cpp b/source/Host/common/XML.cpp
index c3169bd..42e3d4b 100644
--- a/source/Host/common/XML.cpp
+++ b/source/Host/common/XML.cpp
@@ -151,6 +151,18 @@
     return llvm::StringRef();
 }
 
+bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value,
+                                          uint64_t fail_value, int base) const {
+#if defined(LIBXML2_DEFINED)
+  llvm::StringRef str_value = GetAttributeValue(name, "");
+#else
+  llvm::StringRef str_value;
+#endif
+  bool success = false;
+  value = StringConvert::ToUInt64(str_value.data(), fail_value, base, &success);
+  return success;
+}
+
 void XMLNode::ForEachChildNode(NodeCallback const &callback) const {
 #if defined(LIBXML2_DEFINED)
   if (IsValid())
diff --git a/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
index b428b70..3827995 100644
--- a/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ b/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -3442,3 +3442,38 @@
   section_data.SetData(buffer_sp);
   return buffer_sp->GetByteSize();
 }
+
+bool ObjectFileELF::AnySegmentHasPhysicalAddress() {
+  size_t header_count = ParseProgramHeaders();
+  for (size_t i = 1; i <= header_count; ++i) {
+    auto header = GetProgramHeaderByIndex(i);
+    if (header->p_paddr != 0)
+      return true;
+  }
+  return false;
+}
+
+std::vector<ObjectFile::LoadableData>
+ObjectFileELF::GetLoadableData(Target &target) {
+  // Create a list of loadable data from loadable segments,
+  // using physical addresses if they aren't all null
+  std::vector<LoadableData> loadables;
+  size_t header_count = ParseProgramHeaders();
+  bool should_use_paddr = AnySegmentHasPhysicalAddress();
+  for (size_t i = 1; i <= header_count; ++i) {
+    LoadableData loadable;
+    auto header = GetProgramHeaderByIndex(i);
+    if (header->p_type != llvm::ELF::PT_LOAD)
+      continue;
+    loadable.Dest = should_use_paddr ? header->p_paddr : header->p_vaddr;
+    if (loadable.Dest == LLDB_INVALID_ADDRESS)
+      continue;
+    if (header->p_filesz == 0)
+      continue;
+    auto segment_data = GetSegmentDataByIndex(i);
+    loadable.Contents = llvm::ArrayRef<uint8_t>(segment_data.GetDataStart(),
+                                                segment_data.GetByteSize());
+    loadables.push_back(loadable);
+  }
+  return loadables;
+}
diff --git a/source/Plugins/ObjectFile/ELF/ObjectFileELF.h b/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
index 2909f4e..7b2a379 100644
--- a/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
+++ b/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
@@ -161,6 +161,11 @@
 
   void RelocateSection(lldb_private::Section *section) override;
 
+protected:
+
+  std::vector<LoadableData>
+  GetLoadableData(lldb_private::Target &target) override;
+
 private:
   ObjectFileELF(const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp,
                 lldb::offset_t data_offset, const lldb_private::FileSpec *file,
@@ -383,6 +388,8 @@
   RefineModuleDetailsFromNote(lldb_private::DataExtractor &data,
                               lldb_private::ArchSpec &arch_spec,
                               lldb_private::UUID &uuid);
+
+  bool AnySegmentHasPhysicalAddress();
 };
 
 #endif // liblldb_ObjectFileELF_h_
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index b0785a6..30db558 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -21,6 +21,7 @@
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/State.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Host/XML.h"
 #include "lldb/Interpreter/Args.h"
 #include "lldb/Symbol/Symbol.h"
 #include "lldb/Target/MemoryRegionInfo.h"
@@ -81,6 +82,7 @@
       m_supports_qXfer_libraries_read(eLazyBoolCalculate),
       m_supports_qXfer_libraries_svr4_read(eLazyBoolCalculate),
       m_supports_qXfer_features_read(eLazyBoolCalculate),
+      m_supports_qXfer_memory_map_read(eLazyBoolCalculate),
       m_supports_augmented_libraries_svr4_read(eLazyBoolCalculate),
       m_supports_jThreadExtendedInfo(eLazyBoolCalculate),
       m_supports_jLoadedDynamicLibrariesInfos(eLazyBoolCalculate),
@@ -103,7 +105,8 @@
       m_hostname(), m_gdb_server_name(), m_gdb_server_version(UINT32_MAX),
       m_default_packet_timeout(0), m_max_packet_size(0),
       m_qSupported_response(), m_supported_async_json_packets_is_valid(false),
-      m_supported_async_json_packets_sp() {}
+      m_supported_async_json_packets_sp(), m_qXfer_memory_map(),
+      m_qXfer_memory_map_loaded(false) {}
 
 //----------------------------------------------------------------------
 // Destructor
@@ -192,6 +195,13 @@
   return m_supports_qXfer_features_read == eLazyBoolYes;
 }
 
+bool GDBRemoteCommunicationClient::GetQXferMemoryMapReadSupported() {
+  if (m_supports_qXfer_memory_map_read == eLazyBoolCalculate) {
+    GetRemoteQSupported();
+  }
+  return m_supports_qXfer_memory_map_read == eLazyBoolYes;
+}
+
 uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() {
   if (m_max_packet_size == 0) {
     GetRemoteQSupported();
@@ -296,6 +306,7 @@
     m_supports_qXfer_libraries_read = eLazyBoolCalculate;
     m_supports_qXfer_libraries_svr4_read = eLazyBoolCalculate;
     m_supports_qXfer_features_read = eLazyBoolCalculate;
+    m_supports_qXfer_memory_map_read = eLazyBoolCalculate;
     m_supports_augmented_libraries_svr4_read = eLazyBoolCalculate;
     m_supports_qProcessInfoPID = true;
     m_supports_qfProcessInfo = true;
@@ -342,6 +353,7 @@
   m_supports_qXfer_libraries_svr4_read = eLazyBoolNo;
   m_supports_augmented_libraries_svr4_read = eLazyBoolNo;
   m_supports_qXfer_features_read = eLazyBoolNo;
+  m_supports_qXfer_memory_map_read = eLazyBoolNo;
   m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
                                   // not, we assume no limit
 
@@ -377,6 +389,8 @@
       m_supports_qXfer_libraries_read = eLazyBoolYes;
     if (::strstr(response_cstr, "qXfer:features:read+"))
       m_supports_qXfer_features_read = eLazyBoolYes;
+    if (::strstr(response_cstr, "qXfer:memory-map:read+"))
+      m_supports_qXfer_memory_map_read = eLazyBoolYes;
 
     // Look for a list of compressions in the features list e.g.
     // qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-deflate,lzma
@@ -1460,7 +1474,8 @@
     UNUSED_IF_ASSERT_DISABLED(packet_len);
     StringExtractorGDBRemote response;
     if (SendPacketAndWaitForResponse(packet, response, false) ==
-        PacketResult::Success) {
+            PacketResult::Success &&
+        response.GetResponseType() == StringExtractorGDBRemote::eResponse) {
       llvm::StringRef name;
       llvm::StringRef value;
       addr_t addr_value = LLDB_INVALID_ADDRESS;
@@ -1536,8 +1551,134 @@
   if (m_supports_memory_region_info == eLazyBoolNo) {
     error.SetErrorString("qMemoryRegionInfo is not supported");
   }
-  if (error.Fail())
-    region_info.Clear();
+
+  // Try qXfer:memory-map:read to get region information not included in
+  // qMemoryRegionInfo
+  MemoryRegionInfo qXfer_region_info;
+  Status qXfer_error = GetQXferMemoryMapRegionInfo(addr, qXfer_region_info);
+
+  if (error.Fail()) {
+    // If qMemoryRegionInfo failed, but qXfer:memory-map:read succeeded,
+    // use the qXfer result as a fallback
+    if (qXfer_error.Success()) {
+      region_info = qXfer_region_info;
+      error.Clear();
+    } else {
+      region_info.Clear();
+    }
+  } else if (qXfer_error.Success()) {
+    // If both qMemoryRegionInfo and qXfer:memory-map:read succeeded, and if
+    // both regions are the same range, update the result to include the
+    // flash-memory information that is specific to the qXfer result.
+    if (region_info.GetRange() == qXfer_region_info.GetRange()) {
+      region_info.SetFlash(qXfer_region_info.GetFlash());
+      region_info.SetBlocksize(qXfer_region_info.GetBlocksize());
+    }
+  }
+  return error;
+}
+
+Status GDBRemoteCommunicationClient::GetQXferMemoryMapRegionInfo(
+    lldb::addr_t addr, MemoryRegionInfo &region) {
+  Status error = LoadQXferMemoryMap();
+  if (!error.Success())
+    return error;
+  for (const auto &map_region : m_qXfer_memory_map) {
+    if (map_region.GetRange().Contains(addr)) {
+      region = map_region;
+      return error;
+    }
+  }
+  error.SetErrorString("Region not found");
+  return error;
+}
+
+Status GDBRemoteCommunicationClient::LoadQXferMemoryMap() {
+
+  Status error;
+
+  if (m_qXfer_memory_map_loaded)
+    // Already loaded, return success
+    return error;
+
+  if (!XMLDocument::XMLEnabled()) {
+    error.SetErrorString("XML is not supported");
+    return error;
+  }
+
+  if (!GetQXferMemoryMapReadSupported()) {
+    error.SetErrorString("Memory map is not supported");
+    return error;
+  }
+
+  std::string xml;
+  lldb_private::Status lldberr;
+  if (!ReadExtFeature(ConstString("memory-map"), ConstString(""), xml,
+                      lldberr)) {
+    error.SetErrorString("Failed to read memory map");
+    return error;
+  }
+
+  XMLDocument xml_document;
+
+  if (!xml_document.ParseMemory(xml.c_str(), xml.size())) {
+    error.SetErrorString("Failed to parse memory map xml");
+    return error;
+  }
+
+  XMLNode map_node = xml_document.GetRootElement("memory-map");
+  if (!map_node) {
+    error.SetErrorString("Invalid root node in memory map xml");
+    return error;
+  }
+
+  m_qXfer_memory_map.clear();
+
+  map_node.ForEachChildElement([this](const XMLNode &memory_node) -> bool {
+    if (!memory_node.IsElement())
+      return true;
+    if (memory_node.GetName() != "memory")
+      return true;
+    auto type = memory_node.GetAttributeValue("type", "");
+    uint64_t start;
+    uint64_t length;
+    if (!memory_node.GetAttributeValueAsUnsigned("start", start))
+      return true;
+    if (!memory_node.GetAttributeValueAsUnsigned("length", length))
+      return true;
+    MemoryRegionInfo region;
+    region.GetRange().SetRangeBase(start);
+    region.GetRange().SetByteSize(length);
+    if (type == "rom") {
+      region.SetReadable(MemoryRegionInfo::eYes);
+      this->m_qXfer_memory_map.push_back(region);
+    } else if (type == "ram") {
+      region.SetReadable(MemoryRegionInfo::eYes);
+      region.SetWritable(MemoryRegionInfo::eYes);
+      this->m_qXfer_memory_map.push_back(region);
+    } else if (type == "flash") {
+      region.SetFlash(MemoryRegionInfo::eYes);
+      memory_node.ForEachChildElement(
+          [&region](const XMLNode &prop_node) -> bool {
+            if (!prop_node.IsElement())
+              return true;
+            if (prop_node.GetName() != "property")
+              return true;
+            auto propname = prop_node.GetAttributeValue("name", "");
+            if (propname == "blocksize") {
+              uint64_t blocksize;
+              if (prop_node.GetElementTextAsUnsigned(blocksize))
+                region.SetBlocksize(blocksize);
+            }
+            return true;
+          });
+      this->m_qXfer_memory_map.push_back(region);
+    }
+    return true;
+  });
+
+  m_qXfer_memory_map_loaded = true;
+
   return error;
 }
 
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 4591315..8898767 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -355,6 +355,8 @@
 
   bool GetQXferFeaturesReadSupported();
 
+  bool GetQXferMemoryMapReadSupported();
+
   LazyBool SupportsAllocDeallocMemory() // const
   {
     // Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -545,6 +547,7 @@
   LazyBool m_supports_qXfer_libraries_read;
   LazyBool m_supports_qXfer_libraries_svr4_read;
   LazyBool m_supports_qXfer_features_read;
+  LazyBool m_supports_qXfer_memory_map_read;
   LazyBool m_supports_augmented_libraries_svr4_read;
   LazyBool m_supports_jThreadExtendedInfo;
   LazyBool m_supports_jLoadedDynamicLibrariesInfos;
@@ -588,6 +591,9 @@
   bool m_supported_async_json_packets_is_valid;
   lldb_private::StructuredData::ObjectSP m_supported_async_json_packets_sp;
 
+  std::vector<MemoryRegionInfo> m_qXfer_memory_map;
+  bool m_qXfer_memory_map_loaded;
+
   bool GetCurrentProcessInfo(bool allow_lazy_pid = true);
 
   bool GetGDBServerVersion();
@@ -610,6 +616,11 @@
                                 llvm::MutableArrayRef<uint8_t> &buffer,
                                 size_t offset);
 
+  Status LoadQXferMemoryMap();
+
+  Status GetQXferMemoryMapRegionInfo(lldb::addr_t addr,
+                                     MemoryRegionInfo &region);
+
 private:
   DISALLOW_COPY_AND_ASSIGN(GDBRemoteCommunicationClient);
 };
diff --git a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 0823add..93e08b5 100644
--- a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -59,6 +59,7 @@
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Target/ABI.h"
 #include "lldb/Target/DynamicLoader.h"
+#include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/SystemRuntime.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Target/TargetList.h"
@@ -256,7 +257,8 @@
       m_addr_to_mmap_size(), m_thread_create_bp_sp(),
       m_waiting_for_attach(false), m_destroy_tried_resuming(false),
       m_command_sp(), m_breakpoint_pc_offset(0),
-      m_initial_tid(LLDB_INVALID_THREAD_ID) {
+      m_initial_tid(LLDB_INVALID_THREAD_ID), m_allow_flash_writes(false),
+      m_erased_flash_ranges() {
   m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit,
                                    "async thread should exit");
   m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue,
@@ -2798,6 +2800,145 @@
   return 0;
 }
 
+Status ProcessGDBRemote::WriteObjectFile(
+    std::vector<ObjectFile::LoadableData> entries) {
+  Status error;
+  // Sort the entries by address because some writes, like those to flash
+  // memory, must happen in order of increasing address.
+  std::stable_sort(
+      std::begin(entries), std::end(entries),
+      [](const ObjectFile::LoadableData a, const ObjectFile::LoadableData b) {
+        return a.Dest < b.Dest;
+      });
+  m_allow_flash_writes = true;
+  error = Process::WriteObjectFile(entries);
+  if (error.Success())
+    error = FlashDone();
+  else
+    // Even though some of the writing failed, try to send a flash done if
+    // some of the writing succeeded so the flash state is reset to normal,
+    // but don't stomp on the error status that was set in the write failure
+    // since that's the one we want to report back.
+    FlashDone();
+  m_allow_flash_writes = false;
+  return error;
+}
+
+bool ProcessGDBRemote::HasErased(FlashRange range) {
+  auto size = m_erased_flash_ranges.GetSize();
+  for (size_t i = 0; i < size; ++i)
+    if (m_erased_flash_ranges.GetEntryAtIndex(i)->Contains(range))
+      return true;
+  return false;
+}
+
+Status ProcessGDBRemote::FlashErase(lldb::addr_t addr, size_t size) {
+  Status status;
+
+  MemoryRegionInfo region;
+  status = GetMemoryRegionInfo(addr, region);
+  if (!status.Success())
+    return status;
+
+  // The gdb spec doesn't say if erasures are allowed across multiple regions,
+  // but we'll disallow it to be safe and to keep the logic simple by worring
+  // about only one region's block size.  DoMemoryWrite is this function's
+  // primary user, and it can easily keep writes within a single memory region
+  if (addr + size > region.GetRange().GetRangeEnd()) {
+    status.SetErrorString("Unable to erase flash in multiple regions");
+    return status;
+  }
+
+  uint64_t blocksize = region.GetBlocksize();
+  if (blocksize == 0) {
+    status.SetErrorString("Unable to erase flash because blocksize is 0");
+    return status;
+  }
+
+  // Erasures can only be done on block boundary adresses, so round down addr
+  // and round up size
+  lldb::addr_t block_start_addr = addr - (addr % blocksize);
+  size += (addr - block_start_addr);
+  if ((size % blocksize) != 0)
+    size += (blocksize - size % blocksize);
+
+  FlashRange range(block_start_addr, size);
+
+  if (HasErased(range))
+    return status;
+
+  // We haven't erased the entire range, but we may have erased part of it.
+  // (e.g., block A is already erased and range starts in A and ends in B).
+  // So, adjust range if necessary to exclude already erased blocks.
+  if (!m_erased_flash_ranges.IsEmpty()) {
+    // Assuming that writes and erasures are done in increasing addr order,
+    // because that is a requirement of the vFlashWrite command.  Therefore,
+    // we only need to look at the last range in the list for overlap.
+    const auto &last_range = *m_erased_flash_ranges.Back();
+    if (range.GetRangeBase() < last_range.GetRangeEnd()) {
+      auto overlap = last_range.GetRangeEnd() - range.GetRangeBase();
+      // overlap will be less than range.GetByteSize() or else HasErased() would
+      // have been true
+      range.SetByteSize(range.GetByteSize() - overlap);
+      range.SetRangeBase(range.GetRangeBase() + overlap);
+    }
+  }
+
+  StreamString packet;
+  packet.Printf("vFlashErase:%" PRIx64 ",%" PRIx64, range.GetRangeBase(),
+                (uint64_t)range.GetByteSize());
+
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response,
+                                              true) ==
+      GDBRemoteCommunication::PacketResult::Success) {
+    if (response.IsOKResponse()) {
+      m_erased_flash_ranges.Insert(range, true);
+    } else {
+      if (response.IsErrorResponse())
+        status.SetErrorStringWithFormat("flash erase failed for 0x%" PRIx64,
+                                        addr);
+      else if (response.IsUnsupportedResponse())
+        status.SetErrorStringWithFormat("GDB server does not support flashing");
+      else
+        status.SetErrorStringWithFormat(
+            "unexpected response to GDB server flash erase packet '%s': '%s'",
+            packet.GetData(), response.GetStringRef().c_str());
+    }
+  } else {
+    status.SetErrorStringWithFormat("failed to send packet: '%s'",
+                                    packet.GetData());
+  }
+  return status;
+}
+
+Status ProcessGDBRemote::FlashDone() {
+  Status status;
+  // If we haven't erased any blocks, then we must not have written anything
+  // either, so there is no need to actually send a vFlashDone command
+  if (m_erased_flash_ranges.IsEmpty())
+    return status;
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse("vFlashDone", response, true) ==
+      GDBRemoteCommunication::PacketResult::Success) {
+    if (response.IsOKResponse()) {
+      m_erased_flash_ranges.Clear();
+    } else {
+      if (response.IsErrorResponse())
+        status.SetErrorStringWithFormat("flash done failed");
+      else if (response.IsUnsupportedResponse())
+        status.SetErrorStringWithFormat("GDB server does not support flashing");
+      else
+        status.SetErrorStringWithFormat(
+            "unexpected response to GDB server flash done packet: '%s'",
+            response.GetStringRef().c_str());
+    }
+  } else {
+    status.SetErrorStringWithFormat("failed to send flash done packet");
+  }
+  return status;
+}
+
 size_t ProcessGDBRemote::DoWriteMemory(addr_t addr, const void *buf,
                                        size_t size, Status &error) {
   GetMaxMemorySize();
@@ -2810,10 +2951,33 @@
     size = max_memory_size;
   }
 
-  StreamString packet;
-  packet.Printf("M%" PRIx64 ",%" PRIx64 ":", addr, (uint64_t)size);
-  packet.PutBytesAsRawHex8(buf, size, endian::InlHostByteOrder(),
-                           endian::InlHostByteOrder());
+  StreamGDBRemote packet;
+
+  MemoryRegionInfo region;
+  Status region_status = GetMemoryRegionInfo(addr, region);
+
+  bool is_flash =
+      region_status.Success() && region.GetFlash() == MemoryRegionInfo::eYes;
+
+  if (is_flash) {
+    if (!m_allow_flash_writes) {
+      error.SetErrorString("Writing to flash memory is not allowed");
+      return 0;
+    }
+    // Keep the write within a flash memory region
+    if (addr + size > region.GetRange().GetRangeEnd())
+      size = region.GetRange().GetRangeEnd() - addr;
+    // Flash memory must be erased before it can be written
+    error = FlashErase(addr, size);
+    if (!error.Success())
+      return 0;
+    packet.Printf("vFlashWrite:%" PRIx64 ":", addr);
+    packet.PutEscapedBytes(buf, size);
+  } else {
+    packet.Printf("M%" PRIx64 ",%" PRIx64 ":", addr, (uint64_t)size);
+    packet.PutBytesAsRawHex8(buf, size, endian::InlHostByteOrder(),
+                             endian::InlHostByteOrder());
+  }
   StringExtractorGDBRemote response;
   if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response,
                                               true) ==
diff --git a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 42d1c4e..f0b68b2 100644
--- a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -144,6 +144,9 @@
   size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
                       Status &error) override;
 
+  Status
+  WriteObjectFile(std::vector<ObjectFile::LoadableData> entries) override;
+
   size_t DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size,
                        Status &error) override;
 
@@ -302,6 +305,11 @@
   int64_t m_breakpoint_pc_offset;
   lldb::tid_t m_initial_tid; // The initial thread ID, given by stub on attach
 
+  bool m_allow_flash_writes;
+  using FlashRangeVector = lldb_private::RangeVector<lldb::addr_t, size_t>;
+  using FlashRange = FlashRangeVector::Entry;
+  FlashRangeVector m_erased_flash_ranges;
+
   //----------------------------------------------------------------------
   // Accessors
   //----------------------------------------------------------------------
@@ -408,6 +416,12 @@
 
   Status UpdateAutomaticSignalFiltering() override;
 
+  Status FlashErase(lldb::addr_t addr, size_t size);
+
+  Status FlashDone();
+
+  bool HasErased(FlashRange range);
+
 private:
   //------------------------------------------------------------------
   // For ProcessGDBRemote only
diff --git a/source/Symbol/ObjectFile.cpp b/source/Symbol/ObjectFile.cpp
index 2129a44..a0cd94a 100644
--- a/source/Symbol/ObjectFile.cpp
+++ b/source/Symbol/ObjectFile.cpp
@@ -16,7 +16,6 @@
 #include "lldb/Symbol/ObjectContainer.h"
 #include "lldb/Symbol/SymbolFile.h"
 #include "lldb/Target/Process.h"
-#include "lldb/Target/RegisterContext.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/DataBuffer.h"
@@ -648,41 +647,31 @@
   return ConstString(ss.GetString());
 }
 
-Status ObjectFile::LoadInMemory(Target &target, bool set_pc) {
-  Status error;
-  ProcessSP process = target.CalculateProcess();
-  if (!process)
-    return Status("No Process");
-  if (set_pc && !GetEntryPointAddress().IsValid())
-    return Status("No entry address in object file");
-
+std::vector<ObjectFile::LoadableData>
+ObjectFile::GetLoadableData(Target &target) {
+  std::vector<LoadableData> loadables;
   SectionList *section_list = GetSectionList();
   if (!section_list)
-    return Status("No section in object file");
+    return loadables;
+  // Create a list of loadable data from loadable sections
   size_t section_count = section_list->GetNumSections(0);
   for (size_t i = 0; i < section_count; ++i) {
+    LoadableData loadable;
     SectionSP section_sp = section_list->GetSectionAtIndex(i);
-    addr_t addr = target.GetSectionLoadList().GetSectionLoadAddress(section_sp);
-    if (addr != LLDB_INVALID_ADDRESS) {
-      DataExtractor section_data;
-      // We can skip sections like bss
-      if (section_sp->GetFileSize() == 0)
-        continue;
-      section_sp->GetSectionData(section_data);
-      lldb::offset_t written = process->WriteMemory(
-          addr, section_data.GetDataStart(), section_data.GetByteSize(), error);
-      if (written != section_data.GetByteSize())
-        return error;
-    }
+    loadable.Dest =
+        target.GetSectionLoadList().GetSectionLoadAddress(section_sp);
+    if (loadable.Dest == LLDB_INVALID_ADDRESS)
+      continue;
+    // We can skip sections like bss
+    if (section_sp->GetFileSize() == 0)
+      continue;
+    DataExtractor section_data;
+    section_sp->GetSectionData(section_data);
+    loadable.Contents = llvm::ArrayRef<uint8_t>(section_data.GetDataStart(),
+                                                section_data.GetByteSize());
+    loadables.push_back(loadable);
   }
-  if (set_pc) {
-    ThreadList &thread_list = process->GetThreadList();
-    ThreadSP curr_thread(thread_list.GetSelectedThread());
-    RegisterContextSP reg_context(curr_thread->GetRegisterContext());
-    Address file_entry = GetEntryPointAddress();
-    reg_context->SetPC(file_entry.GetLoadAddress(&target));
-  }
-  return error;
+  return loadables;
 }
 
 void ObjectFile::RelocateSection(lldb_private::Section *section)
diff --git a/source/Target/Process.cpp b/source/Target/Process.cpp
index c40894b..e9809d5 100644
--- a/source/Target/Process.cpp
+++ b/source/Target/Process.cpp
@@ -2533,6 +2533,17 @@
   return 0;
 }
 
+Status Process::WriteObjectFile(std::vector<ObjectFile::LoadableData> entries) {
+  Status error;
+  for (const auto &Entry : entries) {
+    WriteMemory(Entry.Dest, Entry.Contents.data(), Entry.Contents.size(),
+                error);
+    if (!error.Success())
+      break;
+  }
+  return error;
+}
+
 #define USE_ALLOCATE_MEMORY_CACHE 1
 addr_t Process::AllocateMemory(size_t size, uint32_t permissions,
                                Status &error) {