| import lldb | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.gdbclientutils import * | |
| from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase | |
| class TestAddressSpaceMemoryRead(GDBRemoteTestBase): | |
| """ | |
| End-to-end test that the same numeric address read from two different | |
| address spaces returns different bytes. The server advertises | |
| "address-spaces+" in qSupported, reports the spaces via "jAddressSpacesInfo", | |
| and reads memory with an optional "address_space:<hex-id>;" suffix on the | |
| standard memory packet. | |
| """ | |
| def test(self): | |
| address_spaces_json = ( | |
| '[{"name":"global","space_id":1,"is_thread_specific":false},' | |
| '{"name":"local","space_id":26,"is_thread_specific":true}]' | |
| ) | |
| class MyResponder(MockGDBServerResponder): | |
| def qSupported(self, client_supported): | |
| return "PacketSize=3fff;QStartNoAckMode+;address-spaces+" | |
| def qHostInfo(self): | |
| return "ptrsize:8;endian:little;" | |
| def _bytes_for_space(self, space): | |
| if space == 1: | |
| return "aabbccdd" | |
| if space == 26: | |
| return "11223344" | |
| return "E01" | |
| def __init__(self): | |
| super().__init__() | |
| self.reads = [] | |
| def _respond_impl(self, packet): | |
| # The base dispatcher can't parse the "address_space:<hex-id>;" | |
| # suffix, so handle suffixed reads here. | |
| if packet and packet[0] in ("m", "x") and "address_space:" in packet: | |
| self.reads.append(packet) | |
| space = 0 | |
| for field in packet[1:].split(";"): | |
| key, _, value = field.partition(":") | |
| if key == "address_space": | |
| space = int(value, 16) | |
| return self._bytes_for_space(space) | |
| return super()._respond_impl(packet) | |
| def x(self, addr, length): | |
| # Force the client onto the hex "m" read path. | |
| return "" | |
| def other(self, packet): | |
| if packet == "jAddressSpacesInfo": | |
| return escape_binary(address_spaces_json) | |
| return "" | |
| self.server.responder = MyResponder() | |
| target = self.dbg.CreateTarget("") | |
| process = self.connect(target) | |
| error = lldb.SBError() | |
| # Same numeric address, two spaces (global == id 1, local == id 26). | |
| global_bytes = process.ReadMemory(lldb.SBProcessAddress(0x1000, 1), 4, error) | |
| self.assertSuccess(error) | |
| self.assertEqual(global_bytes, b"\xaa\xbb\xcc\xdd") | |
| # "local" is thread specific, so reading it needs a thread. | |
| thread = process.GetThreadAtIndex(0) | |
| self.assertTrue(thread.IsValid()) | |
| local_bytes = process.ReadMemory( | |
| lldb.SBProcessAddress(0x1000, 26, thread), 4, error | |
| ) | |
| self.assertSuccess(error) | |
| self.assertEqual(local_bytes, b"\x11\x22\x33\x44") | |
| # Same address, different address space, different bytes. | |
| self.assertNotEqual(global_bytes, local_bytes) | |
| # Ids go out in base 16, like the other numeric values in the packet. | |
| self.assertEqual(len(self.server.responder.reads), 2) | |
| self.assertIn("address_space:1;", self.server.responder.reads[0]) | |
| self.assertIn("address_space:1a;", self.server.responder.reads[1]) | |
| # Only the thread specific read carries a "thread:" field, and it names | |
| # the thread that was asked for. | |
| self.assertNotIn("thread:", self.server.responder.reads[0]) | |
| self.assertIn( | |
| "thread:%x;" % thread.GetThreadID(), self.server.responder.reads[1] | |
| ) | |
| # A thread specific space without a thread is an error, not a read. | |
| process.ReadMemory(lldb.SBProcessAddress(0x1000, 26), 4, error) | |
| self.assertTrue(error.Fail()) | |
| self.assertIn("thread specific", error.GetCString()) | |
| # Address spaces can be resolved by name, and reading through the | |
| # resolved id gives the same bytes as reading through the id directly. | |
| global_id = process.GetAddressSpaceID("global", error) | |
| self.assertSuccess(error) | |
| self.assertEqual(global_id, 1) | |
| self.assertEqual( | |
| process.ReadMemory(lldb.SBProcessAddress(0x1000, global_id), 4, error), | |
| global_bytes, | |
| ) | |
| self.assertSuccess(error) | |
| self.assertEqual(process.GetAddressSpaceID("local", error), 26) | |
| self.assertSuccess(error) | |
| # An unknown name is an error rather than a silent default. | |
| process.GetAddressSpaceID("nonexistent", error) | |
| self.assertTrue(error.Fail()) | |
| # So is no name at all. | |
| process.GetAddressSpaceID(None, error) | |
| self.assertTrue(error.Fail()) |