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/TestGDBRemoteClient.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestGDBRemoteClient.py @@ -0,0 +1,13 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from gdbclientutils import * + + +class TestGDBRemoteClient(GDBRemoteTestBase): + + @no_debug_info_test + def test_connect(self): + """Test connecting to a remote gdb server""" + target = self.createTarget("a.yaml") + process = self.connect(target) 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,62 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from gdbclientutils import * + + +class TestGDBRemoteLoad(GDBRemoteTestBase): + + @no_debug_info_test + 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" + ]) + + @no_debug_info_test + 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: packages/Python/lldbsuite/test/functionalities/gdb_remote_client/a.yaml =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/gdb_remote_client/a.yaml @@ -0,0 +1,34 @@ +!ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_ARM +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x4 + Content: "c3c3c3c3" + - Name: .data + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x2000 + AddressAlign: 0x4 + Content: "3232" +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_X, PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Align: 0x4 + Sections: + - Section: .text + - Type: PT_LOAD + Flags: [ PF_R, PF_W ] + VAddr: 0x2000 + PAddr: 0x1004 + Align: 0x4 + Sections: + - Section: .data Index: packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/gdb_remote_client/gdbclientutils.py @@ -0,0 +1,342 @@ +import os +import os.path +import subprocess +import threading +import socket +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbtest_config + + +def yaml2obj_executable(): + # Tries to find yaml2obj at the same folder as the lldb + path = os.path.join(os.path.dirname(lldbtest_config.lldbExec), "yaml2obj") + if os.path.exists(path): + return path + raise Exception("yaml2obj executable not found") + + +def yaml2elf(yaml_path, elf_path): + yaml2obj = yaml2obj_executable() + command = [yaml2obj, "-o=%s" % elf_path, yaml_path] + system([command]) + + +def checksum(message): + check = 0 + for c in message: + check += ord(c) + return check % 256 + + +def frame_packet(message): + return "$%s#%02x" % (message, checksum(message)) + + +def escape_binary(message): + out = "" + for c in message: + d = ord(c) + if d in (0x23, 0x24, 0x7d): + out += chr(0x7d) + out += chr(d ^ 0x20) + else: + out += c + return out + + +def hex_encode_bytes(message): + out = "" + for c in message: + out += "%02x" % ord(c) + return out + + +def hex_decode_bytes(hex_bytes): + out = "" + hex_len = len(hex_bytes) + while i < hex_len - 1: + out += chr(int(hex_bytes[i:i + 2]), 16) + i += 2 + return out + + +class MockGDBServerResponder: + + registerCount = 40 + packetLog = None + + def __init__(self): + self.packetLog = [] + + def respond(self, packet): + self.packetLog.append(packet) + if packet == "g": + return self.readRegisters() + if packet[0] == "G": + return self.writeRegisters(packet[1:]) + if packet[0] == "p": + return self.readRegister(int(packet[1:], 16)) + if packet[0] == "P": + register, value = packet[1:].split("=") + return self.readRegister(int(register, 16), value) + if packet[0] == "m": + addr, length = [int(x, 16) for x in packet[1:].split(',')] + return self.readMemory(addr, length) + if packet[0] == "M": + location, encoded_data = packet[1:].split(":") + addr, length = [int(x, 16) for x in location.split(',')] + return self.writeMemory(addr, encoded_data) + if packet[0:7] == "qSymbol": + return self.qSymbol(packet[8:]) + if packet[0:10] == "qSupported": + return self.qSupported(packet[11:].split(";")) + if packet == "qfThreadInfo": + return self.qfThreadInfo() + if packet == "qC": + return self.qC() + if packet == "?": + return self.haltReason() + if packet[0] == "H": + return self.selectThread(packet[1], int(packet[2:], 16)) + if packet[0:6] == "qXfer:": + obj, read, annex, location = packet[6:].split(":") + offset, length = [int(x, 16) for x in location.split(',')] + data, has_more = self.qXferRead(obj, annex, offset, length) + if data is not None: + return self._qXferResponse(data, has_more) + return "" + return self.other(packet) + + def readRegisters(self): + return "xxxxxxxx" * self.registerCount + + def readRegister(self, register): + return "xxxxxxxx" + + def writeRegisters(self, registers_hex): + return "OK" + + def writeRegister(self, register, value_hex): + return "OK" + + def readMemory(self, addr, length): + return "00" * length + + def writeMemory(self, addr, data_hex): + return "OK" + + def qSymbol(self, symbol_args): + return "OK" + + def qSupported(self, client_supported): + return "PacketSize=3fff;QStartNoAckMode+" + + def qfThreadInfo(self): + return "l" + + def qC(self): + return "QC0" + + def haltReason(self): + # SIGINT is 2, return type is 2 digit hex string + return "S02" + + def qXferRead(self, obj, annex, offset, length): + return None, False + + def _qXferResponse(self, data, has_more): + return "%s%s" % ("m" if has_more else "l", escape_binary(data)) + + def selectThread(self, op, thread_id): + return "OK" + + def other(self, packet): + # empty string means unsupported + return "" + + +class MockGDBServer: + + responder = None + port = 0 + _socket = None + _client = None + _thread = None + _incomingPacket = None + _incomingChecksum = None + _shouldSendAck = True + _isExpectingAck = False + + def __init__(self, port = 0): + self.responder = MockGDBServerResponder() + self.port = port + self._socket = socket.socket() + + def start(self): + # Block until the socket is up, so self.port is available immediately. + # Then start a thread that waits for a client connection. + addr = ("127.0.0.1", self.port) + self._socket.bind(addr) + self.port = self._socket.getsockname()[1] + self._socket.listen(0) + self._thread = threading.Thread(target=self._run) + self._thread.start() + + def stop(self): + if self._client is not None: + self._client.shutdown(socket.SHUT_RDWR) + self._client.close() + # Would call self._socket.shutdown, but it blocks forever for some + # unknown reason. close() works just fine. + self._socket.close() + self._thread.join() + self._thread = None + + def _run(self): + # For testing purposes, we only need to worry about one client + # connecting just one time. + try: + # accept() is stubborn and won't fail even when the socket is + # shutdown, so we'll use a timeout + self._socket.settimeout(2.0) + client, client_addr = self._socket.accept() + self._client = client + # The connected client inherits its timeout from self._socket, + # but we'll use a blocking socket for the client + self._client.settimeout(None) + except: + return + self._shouldSendAck = True + self._isExpectingAck = False + data = None + while True: + try: + data = self._client.recv(4096) + if data is None or len(data) == 0: + break + except Exception as e: + self._client.close() + break + self._receive(data) + + def _receive(self, data): + i = 0 + data_len = len(data) + while i < data_len: + # If we haven't set _incomingPacket to anything yet, it means we're + # expecting the start of a new packet. + if self._incomingPacket is None: + if data[i] == '+': + if self._isExpectingAck: + # We're expecting an ack from the client, so ignore it. + self._isExpectingAck = False + else: + # Not expecting an ack, so just ack back. + self._client.sendall('+') + i += 1 + elif data[i] == '$': + self._incomingPacket = "" + i += 1 + else: + # Unexpected byte, closing connection to indicate error. + self._client.close() + return + # If we haven't set _incomingChecksum to anything yet, it means + # we're collecting bytes, waiting for a # to indicate the end of + # packet data. + elif self._incomingChecksum is None: + while i < data_len: + if data[i] == '#': + self._incomingChecksum = "" + i += 1 + break + self._incomingPacket += data[i] + i += 1 + # If we have set _incomingChecksum, then we're collecting the + # two bytes of the checksum string. + else: + while i < data_len and len(self._incomingChecksum) < 2: + self._incomingChecksum += data[i] + i += 1 + if len(self._incomingChecksum) == 2: + check = None + try: + check = int(self._incomingChecksum, 16) + except ValueError: + # Non-hex checksum, closing connection. + self._client.close() + return + if check != checksum(self._incomingPacket): + # Mismatching checksums, closing connection. + # Since we're using TCP transport, the checksum can + # only be wrong if the client did something wrong. + self._client.close() + return + packet = self._incomingPacket + self._incomingPacket = None + self._incomingChecksum = None + self._handlePacket(packet) + + def _handlePacket(self, packet): + response = "" + # We'll handle the ack stuff here since it's not something any of the + # tests will be concerned about, and it'll get turned off quicly anyway. + if self._shouldSendAck: + self._client.sendall('+') + self._isExpectingAck = True + if packet == "QStartNoAckMode": + self._shouldSendAck = False + response = "OK" + elif self.responder is not None: + # Delegate everything else to our responder + response = self.responder.respond(packet) + # Handle packet framing since we don't want to bother tests with it. + framed = frame_packet(response) + self._client.sendall(framed) + + +class GDBRemoteTestBase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + server = None + temp_files = None + + def setUp(self): + TestBase.setUp(self) + self.temp_files = [] + self.server = MockGDBServer() + self.server.start() + + def tearDown(self): + for temp_file in self.temp_files: + self.RemoveTempFile(temp_file) + self.server.stop() + self.temp_files = [] + TestBase.tearDown(self) + + def createTarget(self, yaml_path): + yaml_base, ext = os.path.splitext(yaml_path) + elf_path = "%s.elf" % yaml_base + yaml2elf(yaml_path, elf_path) + self.temp_files.append(elf_path) + return self.dbg.CreateTarget(elf_path) + + def connect(self, target): + listener = self.dbg.GetListener() + error = lldb.SBError() + url = "connect://localhost:%d" % self.server.port + process = target.ConnectRemote(listener, url, "gdb-remote", error) + self.assertTrue(error.Success(), error.description) + self.assertTrue(process, PROCESS_IS_VALID) + + def assertPacketLogContains(self, packets): + i = 0 + j = 0 + log = self.server.responder.packetLog + while i < len(packets) and j < len(log): + if log[j] == packets[i]: + i += 1 + j += 1 + if i < len(packets): + self.fail("Did not receive: %s" % packets[i]) 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/GDBRemoteCommunication.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -141,6 +141,17 @@ void DumpHistory(Stream &strm); + //------------------------------------------------------------------ + // Write the bytes from buf into stream, including escape sequences + // for certain bytes as described by the gdb server protocol. + // + // Most commands escape using hex strings, but a few commands send + // nearly-raw binary, only escaping special characters such as #, $, + // and }. + //------------------------------------------------------------------ + static void WriteEscapedBinary(StreamString &stream, const void *buf, + size_t size); + protected: class History { public: Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -274,6 +274,23 @@ return result; } +void GDBRemoteCommunication::WriteEscapedBinary(StreamString &stream, + const void *buf, + size_t size) { + const uint8_t *bytes = (const uint8_t *)buf; + const uint8_t *end = bytes + size; + const uint8_t escape = 0x7d; + for (; bytes != end; ++bytes) { + if (*bytes == 0x23 || *bytes == 0x24 || *bytes == 0x7d) { + const uint8_t x = (*bytes) ^ 0x20; + stream.Write(&escape, 1); + stream.Write(&x, 1); + }else{ + stream.Write(bytes, 1); + } + } +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunication::ReadPacketWithOutputSupport( StringExtractorGDBRemote &response, Timeout timeout, 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; Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -81,6 +81,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), @@ -192,6 +193,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 +304,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 +351,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 +387,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 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,12 @@ 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; + std::vector m_memory_map; + //---------------------------------------------------------------------- // Accessors //---------------------------------------------------------------------- @@ -408,6 +418,16 @@ Status UpdateAutomaticSignalFiltering() override; + bool GetMemoryMap(std::vector ®ion_list); + + Status FlashErase(lldb::addr_t addr, size_t size); + + Status FlashDone(); + + bool HasErased(FlashRange range); + + lldb::MemoryRegionInfoSP GetMemoryMapRegion(lldb::addr_t addr); + 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_memory_map() { m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, "async thread should exit"); m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, @@ -2798,6 +2800,138 @@ return 0; } +MemoryRegionInfoSP ProcessGDBRemote::GetMemoryMapRegion(lldb::addr_t addr) { + if (m_memory_map.empty()) + GetMemoryMap(m_memory_map); + for (const auto ®ion : m_memory_map) + if (region->GetRange().Contains(addr)) + return region; + return nullptr; +} + +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; + + MemoryRegionInfoSP region_sp = GetMemoryMapRegion(addr); + + // 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_sp->GetRange().GetRangeEnd()){ + status.SetErrorString("Unable to erase flash in multiple regions"); + return status; + } + + uint64_t blocksize = region_sp->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(); @@ -2811,15 +2945,37 @@ } StreamString packet; - packet.Printf("M%" PRIx64 ",%" PRIx64 ":", addr, (uint64_t)size); - packet.PutBytesAsRawHex8(buf, size, endian::InlHostByteOrder(), - endian::InlHostByteOrder()); + + MemoryRegionInfoSP region_sp = GetMemoryMapRegion(addr); + + bool is_flash = region_sp != nullptr && region_sp->GetFlash() == + MemoryRegionInfo::eYes; + if (is_flash) { + // Keep the write within a flash memory region + if (addr + size > region_sp->GetRange().GetRangeEnd()) + size = region_sp->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); + GDBRemoteCommunication::WriteEscapedBinary(packet, 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, @@ -4453,6 +4609,78 @@ return m_register_info.GetNumRegisters() > 0; } +bool ProcessGDBRemote::GetMemoryMap( + std::vector ®ion_list) { + + if (!XMLDocument::XMLEnabled()) + return false; + + auto &comm = m_gdb_comm; + + if (!comm.GetQXferMemoryMapReadSupported()) + return false; + + std::string xml; + lldb_private::Status lldberr; + if (!comm.ReadExtFeature(ConstString("memory-map"), + ConstString(""), + xml, lldberr)) + return false; + + XMLDocument xml_document; + + if (!xml_document.ParseMemory(xml.c_str(), xml.size())) + return false; + + XMLNode map_node = xml_document.GetRootElement("memory-map"); + if (!map_node) + return false; + + map_node.ForEachChildElement([®ion_list](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; + MemoryRegionInfoSP region_sp(new MemoryRegionInfo()); + region_sp->GetRange().SetRangeBase(start); + region_sp->GetRange().SetByteSize(length); + if (type == "rom") { + region_sp->SetReadable(MemoryRegionInfo::eYes); + region_list.push_back(region_sp); + } else if (type == "ram") { + region_sp->SetReadable(MemoryRegionInfo::eYes); + region_sp->SetWritable(MemoryRegionInfo::eYes); + region_list.push_back(region_sp); + } else if (type == "flash") { + region_sp->SetFlash(MemoryRegionInfo::eYes); + memory_node.ForEachChildElement([®ion_sp](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_sp->SetBlocksize(blocksize); + } + return true; + }); + region_list.push_back(region_sp); + } + return true; + }); + + return true; +} + Status ProcessGDBRemote::GetLoadedModuleList(LoadedModuleInfoList &list) { // Make sure LLDB has an XML parser it can use first if (!XMLDocument::XMLEnabled()) 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()); Index: unittests/Process/gdb-remote/CMakeLists.txt =================================================================== --- unittests/Process/gdb-remote/CMakeLists.txt +++ unittests/Process/gdb-remote/CMakeLists.txt @@ -1,6 +1,7 @@ add_lldb_unittest(ProcessGdbRemoteTests GDBRemoteClientBaseTest.cpp GDBRemoteCommunicationClientTest.cpp + GDBRemoteCommunicationTest.cpp GDBRemoteTestUtils.cpp LINK_LIBS Index: unittests/Process/gdb-remote/GDBRemoteCommunicationTest.cpp =================================================================== --- /dev/null +++ unittests/Process/gdb-remote/GDBRemoteCommunicationTest.cpp @@ -0,0 +1,54 @@ +//===-- GDBRemoteCommunicationTest.cpp --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include + +#include "GDBRemoteTestUtils.h" +#include "lldb/Utility/StreamString.h" + +using namespace lldb_private::process_gdb_remote; +using namespace lldb_private; + +TEST(GDBRemoteCommunicationTest, WriteEscapedBinary) { + StreamString escaped; + + // Nothing gets escaped + // Verify null and other control chars don't cause problems + const uint8_t data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + GDBRemoteCommunication::WriteEscapedBinary(escaped, data, sizeof(data)); + ASSERT_EQ(sizeof(data), escaped.GetSize()); + ASSERT_EQ(0x00, escaped.GetString().data()[0]); + ASSERT_EQ(0x03, escaped.GetString().data()[3]); + ASSERT_EQ(0x07, escaped.GetString().data()[7]); + + // 0x23 and 0x24 should be escaped + escaped.Clear(); + const uint8_t data2[] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; + GDBRemoteCommunication::WriteEscapedBinary(escaped, data2, sizeof(data)); + ASSERT_EQ(sizeof(data) + 2, escaped.GetSize()); + ASSERT_EQ(0x20, escaped.GetString().data()[0]); + ASSERT_EQ(0x7d, escaped.GetString().data()[3]); + ASSERT_EQ(0x23 ^ 0x20, escaped.GetString().data()[4]); + ASSERT_EQ(0x7d, escaped.GetString().data()[5]); + ASSERT_EQ(0x24 ^ 0x20, escaped.GetString().data()[6]); + ASSERT_EQ(0x25, escaped.GetString().data()[7]); + ASSERT_EQ(0x27, escaped.GetString().data()[9]); + + // 0x7d should be escaped + escaped.Clear(); + const uint8_t data3[] = {0x7b, 0x74, 0x65, 0x73, 0x74, 0x7d}; + GDBRemoteCommunication::WriteEscapedBinary(escaped, data3, sizeof(data)); + ASSERT_EQ(sizeof(data) + 1, escaped.GetSize()); + ASSERT_EQ(0x7b, escaped.GetString().data()[0]); + ASSERT_EQ(0x74, escaped.GetString().data()[1]); + ASSERT_EQ(0x65, escaped.GetString().data()[2]); + ASSERT_EQ(0x73, escaped.GetString().data()[3]); + ASSERT_EQ(0x74, escaped.GetString().data()[4]); + ASSERT_EQ(0x7d, escaped.GetString().data()[5]); + ASSERT_EQ(0x7d ^ 0x20, escaped.GetString().data()[6]); +}