Index: include/lldb/Host/XML.h =================================================================== --- include/lldb/Host/XML.h +++ 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); Index: include/lldb/Target/MemoryRegionInfo.h =================================================================== --- include/lldb/Target/MemoryRegionInfo.h +++ 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; }; } Index: include/lldb/Target/Process.h =================================================================== --- include/lldb/Target/Process.h +++ include/lldb/Target/Process.h @@ -1898,6 +1898,38 @@ bool is_signed, Scalar &scalar, Status &error); + //------------------------------------------------------------------ + /// Inform the process that several memory writes are about to + /// happen as a group. + /// + /// Allows a subclass to prepare for a batch of writes. Most times + /// this function will simply return true because no preparation is + /// required. However, cases such as flash memory writes over a gdb + /// connection require a sequence of erase/write/done commands, and + /// letting the process know a batch is coming allows it to dealy + /// issuing any done command until the EndWriteMemoryBatch() method + /// is called. + /// + /// @return + /// true on success, false on failure + //------------------------------------------------------------------ + virtual bool BeginWriteMemoryBatch() { return true; } + + //------------------------------------------------------------------ + /// Inform the process that a group of memory writes is complete. + /// + /// Allows a subclass to finalize a batch of writes. Most times + /// this function will simply return true because no final operation + /// is required. However, cases such as flash memory writes over a + /// gdb connection require a sequence of erase/write/done commands, + /// and this method informs the process that a done command should + /// be issued. + /// + /// @return + /// true on success, false on failure + //------------------------------------------------------------------ + virtual bool EndWriteMemoryBatch() { return true; } + //------------------------------------------------------------------ /// Write memory to a process. /// Index: packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteLoad.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteLoad.py @@ -0,0 +1,60 @@ +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" + ]) + + 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 = """ + + + + 0x100 + + + +""" + + 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" + ]) Index: source/Host/common/XML.cpp =================================================================== --- source/Host/common/XML.cpp +++ 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()) Index: source/Plugins/ObjectFile/ELF/ObjectFileELF.h =================================================================== --- source/Plugins/ObjectFile/ELF/ObjectFileELF.h +++ source/Plugins/ObjectFile/ELF/ObjectFileELF.h @@ -383,6 +383,12 @@ RefineModuleDetailsFromNote(lldb_private::DataExtractor &data, lldb_private::ArchSpec &arch_spec, lldb_private::UUID &uuid); + + bool AnySegmentHasPhysicalAddress(); + + const elf::ELFProgramHeader *GetSectionSegment(lldb::SectionSP section_sp); + + lldb::addr_t GetSectionPhysicalAddress(lldb::SectionSP section_sp); }; #endif // liblldb_ObjectFileELF_h_ Index: source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp =================================================================== --- source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -865,7 +865,7 @@ // of the sections that have SHF_ALLOC in their flag bits. SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); if (section_sp && section_sp->Test(SHF_ALLOC)) { - lldb::addr_t load_addr = section_sp->GetFileAddress(); + lldb::addr_t load_addr = GetSectionPhysicalAddress(section_sp); // We don't want to update the load address of a section with type // eSectionTypeAbsoluteAddress as they already have the absolute load // address @@ -3507,3 +3507,41 @@ 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; +} + +const elf::ELFProgramHeader *ObjectFileELF::GetSectionSegment( + SectionSP section_sp) { + auto section_size = section_sp->GetFileSize(); + if (section_size == 0) + section_size = 1; + size_t header_count = ParseProgramHeaders(); + for (size_t i = 1; i <= header_count; ++i) { + auto header = GetProgramHeaderByIndex(i); + if (section_sp->GetFileOffset() >= header->p_offset && + section_sp->GetFileOffset() + section_size <= header->p_offset + + header->p_filesz) + return header; + } + return nullptr; +} + +addr_t ObjectFileELF::GetSectionPhysicalAddress(SectionSP section_sp) { + auto segment = GetSectionSegment(section_sp); + if (segment == nullptr) + return section_sp->GetFileAddress(); + if (segment->p_type != PT_LOAD) + return LLDB_INVALID_ADDRESS; + auto base_address = AnySegmentHasPhysicalAddress() ? segment->p_paddr : + segment->p_vaddr; + return base_address + (section_sp->GetFileOffset() - segment->p_offset); +} Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ 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 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 &buffer, size_t offset); + Status LoadQXferMemoryMap(); + + Status GetQXferMemoryMapRegionInfo(lldb::addr_t addr, + MemoryRegionInfo ®ion); + private: DISALLOW_COPY_AND_ASSIGN(GDBRemoteCommunicationClient); }; Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ 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 ®ion) { + 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([®ion](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; } Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -144,6 +144,10 @@ size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, Status &error) override; + bool BeginWriteMemoryBatch() override; + + bool EndWriteMemoryBatch() override; + size_t DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, Status &error) override; @@ -302,6 +306,11 @@ int64_t m_breakpoint_pc_offset; lldb::tid_t m_initial_tid; // The initial thread ID, given by stub on attach + bool m_is_batched_memory_write; + using FlashRangeVector = lldb_private::RangeVector; + using FlashRange = FlashRangeVector::Entry; + FlashRangeVector m_erased_flash_ranges; + //---------------------------------------------------------------------- // Accessors //---------------------------------------------------------------------- @@ -408,6 +417,12 @@ Status UpdateAutomaticSignalFiltering() override; + Status FlashErase(lldb::addr_t addr, size_t size); + + Status FlashDone(); + + bool HasErased(FlashRange range); + private: //------------------------------------------------------------------ // For ProcessGDBRemote only Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -62,6 +62,7 @@ #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/TargetList.h" +#include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/ThreadPlanCallFunction.h" #include "lldb/Utility/CleanUp.h" #include "lldb/Utility/FileSpec.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_is_batched_memory_write(false), + m_erased_flash_ranges() { m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, "async thread should exit"); m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, @@ -2798,6 +2800,132 @@ return 0; } +bool ProcessGDBRemote::BeginWriteMemoryBatch() { + m_is_batched_memory_write = true; + return true; +} + +bool ProcessGDBRemote::EndWriteMemoryBatch() { + m_is_batched_memory_write = false; + auto status = FlashDone(); + return status.Success(); +} + +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,16 +2938,40 @@ 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) { + // 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) == GDBRemoteCommunication::PacketResult::Success) { if (response.IsOKResponse()) { error.Clear(); + if (is_flash && !m_is_batched_memory_write) { + error = FlashDone(); + if (!error.Success()) + return 0; + } return size; } else if (response.IsErrorResponse()) error.SetErrorStringWithFormat("memory write failed for 0x%" PRIx64, Index: source/Symbol/ObjectFile.cpp =================================================================== --- source/Symbol/ObjectFile.cpp +++ source/Symbol/ObjectFile.cpp @@ -659,22 +659,47 @@ SectionList *section_list = GetSectionList(); if (!section_list) return Status("No section in object file"); + + // Filter the list of loadable sections + std::vector loadable_sections; size_t section_count = section_list->GetNumSections(0); for (size_t i = 0; i < section_count; ++i) { 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; + if (addr == LLDB_INVALID_ADDRESS) + continue; + // We can skip sections like bss + if (section_sp->GetFileSize() == 0) + continue; + loadable_sections.push_back(section_sp); + } + + // Sort the sections by address because some writes, like those to flash + // memory, must happen in order of increasing address. + std::stable_sort(std::begin(loadable_sections), std::end(loadable_sections), + [&target](const SectionSP a, const SectionSP b){ + addr_t addr_a = target.GetSectionLoadList().GetSectionLoadAddress(a); + addr_t addr_b = target.GetSectionLoadList().GetSectionLoadAddress(b); + return addr_a < addr_b; + }); + + // Do a batch memory write to the process + if (!process->BeginWriteMemoryBatch()) + return Status("Could not start write memory batch"); + for (auto §ion_sp : loadable_sections) { + DataExtractor section_data; + section_sp->GetSectionData(section_data); + addr_t addr = target.GetSectionLoadList().GetSectionLoadAddress(section_sp); + lldb::offset_t written = process->WriteMemory( + addr, section_data.GetDataStart(), section_data.GetByteSize(), error); + if (written != section_data.GetByteSize()) { + process->EndWriteMemoryBatch(); + return error; } } + if (!process->EndWriteMemoryBatch()) + return Status("Could not end write memory batch"); + if (set_pc) { ThreadList &thread_list = process->GetThreadList(); ThreadSP curr_thread(thread_list.GetSelectedThread());