| """ | |
| Test lldb-dap memory support | |
| """ | |
| from base64 import b64decode | |
| import dap_server | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| import lldbdap_testcase | |
| import os | |
| class TestDAP_memory(lldbdap_testcase.DAPTestCaseBase): | |
| @skipIfWindows | |
| def test_memory_refs_variables(self): | |
| """ | |
| Tests memory references for evaluate | |
| """ | |
| program = self.getBuildArtifact("a.out") | |
| self.build_and_launch(program) | |
| source = "main.cpp" | |
| self.source_path = os.path.join(os.getcwd(), source) | |
| self.set_source_breakpoints( | |
| source, | |
| [line_number(source, "// Breakpoint")], | |
| ) | |
| self.continue_to_next_stop() | |
| locals = {l["name"]: l for l in self.dap_server.get_local_variables()} | |
| # Pointers should have memory-references | |
| self.assertIn("memoryReference", locals["rawptr"].keys()) | |
| # Non-pointers should also have memory-references | |
| self.assertIn("memoryReference", locals["not_a_ptr"].keys()) | |
| @skipIfWindows | |
| def test_memory_refs_evaluate(self): | |
| """ | |
| Tests memory references for evaluate | |
| """ | |
| program = self.getBuildArtifact("a.out") | |
| self.build_and_launch(program) | |
| source = "main.cpp" | |
| self.source_path = os.path.join(os.getcwd(), source) | |
| self.set_source_breakpoints( | |
| source, | |
| [line_number(source, "// Breakpoint")], | |
| ) | |
| self.continue_to_next_stop() | |
| self.assertIn( | |
| "memoryReference", | |
| self.dap_server.request_evaluate("rawptr")["body"].keys(), | |
| ) | |
| @skipIfWindows | |
| def test_memory_refs_set_variable(self): | |
| """ | |
| Tests memory references for `setVariable` | |
| """ | |
| program = self.getBuildArtifact("a.out") | |
| self.build_and_launch(program) | |
| source = "main.cpp" | |
| self.source_path = os.path.join(os.getcwd(), source) | |
| self.set_source_breakpoints( | |
| source, | |
| [line_number(source, "// Breakpoint")], | |
| ) | |
| self.continue_to_next_stop() | |
| ptr_value = self.get_local_as_int("rawptr") | |
| self.assertIn( | |
| "memoryReference", | |
| self.set_local("rawptr", ptr_value + 2)["body"].keys(), | |
| ) | |
| @skipIfWindows | |
| def test_readMemory(self): | |
| """ | |
| Tests the 'readMemory' request | |
| """ | |
| program = self.getBuildArtifact("a.out") | |
| self.build_and_launch(program) | |
| source = "main.cpp" | |
| self.source_path = os.path.join(os.getcwd(), source) | |
| self.set_source_breakpoints( | |
| source, | |
| [line_number(source, "// Breakpoint")], | |
| ) | |
| self.continue_to_next_stop() | |
| ptr_deref = self.dap_server.request_evaluate("*rawptr")["body"] | |
| memref = ptr_deref["memoryReference"] | |
| # We can read the complete string | |
| mem = self.dap_server.request_readMemory(memref, 0, 5)["body"] | |
| self.assertEqual(b64decode(mem["data"]), b"dead\0") | |
| # We can read large chunks, potentially returning partial results | |
| mem = self.dap_server.request_readMemory(memref, 0, 4096)["body"] | |
| self.assertEqual(b64decode(mem["data"])[0:5], b"dead\0") | |
| # Use an offset | |
| mem = self.dap_server.request_readMemory(memref, 2, 3)["body"] | |
| self.assertEqual(b64decode(mem["data"]), b"ad\0") | |
| # Reads of size 0 are successful | |
| # VS Code sends those in order to check if a `memoryReference` can actually be dereferenced. | |
| mem = self.dap_server.request_readMemory(memref, 0, 0) | |
| self.assertEqual(mem["success"], True) | |
| self.assertNotIn( | |
| "data", mem["body"], f"expects no data key in response: {mem!r}" | |
| ) | |
| # Reads at offset 0x0 return unreadable bytes | |
| bytes_to_read = 6 | |
| mem = self.dap_server.request_readMemory("0x0", 0, bytes_to_read) | |
| self.assertEqual(mem["body"]["unreadableBytes"], bytes_to_read) | |
| # Reads with invalid address fails. | |
| mem = self.dap_server.request_readMemory("-3204", 0, 10) | |
| self.assertFalse(mem["success"], "expect fail on reading memory.") | |
| self.continue_to_exit() | |
| # Flakey on 32-bit Arm Linux. | |
| @skipIf(oslist=["linux"], archs=["arm$"]) | |
| def test_writeMemory(self): | |
| """ | |
| Tests the 'writeMemory' request | |
| """ | |
| program = self.getBuildArtifact("a.out") | |
| self.build_and_launch(program) | |
| source = "main.cpp" | |
| self.source_path = os.path.join(os.getcwd(), source) | |
| self.set_source_breakpoints( | |
| source, | |
| [line_number(source, "// Breakpoint")], | |
| ) | |
| self.continue_to_next_stop() | |
| # Get the 'not_a_ptr' writable variable reference address. | |
| ptr_deref = self.dap_server.request_evaluate("not_a_ptr")["body"] | |
| memref = ptr_deref["memoryReference"] | |
| # Write the decimal value 50 (0x32 in hexadecimal) to memory. | |
| # This corresponds to the ASCII character '2' and encodes to Base64 as "Mg==". | |
| mem_response = self.writeMemory(memref, 50, 0, True) | |
| self.assertEqual(mem_response["success"], True) | |
| self.assertEqual(mem_response["body"]["bytesWritten"], 1) | |
| # Read back the modified memory and verify that the written data matches | |
| # the expected result. | |
| mem_response = self.dap_server.request_readMemory(memref, 0, 1) | |
| self.assertEqual(mem_response["success"], True) | |
| self.assertEqual(mem_response["body"]["data"], "Mg==") | |
| # Write the decimal value 100 (0x64 in hexadecimal) to memory. | |
| # This corresponds to the ASCII character 'd' and encodes to Base64 as "ZA==". | |
| # allowPartial=False | |
| mem_response = self.writeMemory(memref, 100, 0, False) | |
| self.assertEqual(mem_response["success"], True) | |
| self.assertEqual(mem_response["body"]["bytesWritten"], 1) | |
| # Read back the modified memory and verify that the written data matches | |
| # the expected result. | |
| mem_response = self.dap_server.request_readMemory(memref, 0, 1) | |
| self.assertEqual(mem_response["success"], True) | |
| self.assertEqual(mem_response["body"]["data"], "ZA==") | |
| # Memory write failed for 0x0. | |
| mem_response = self.writeMemory("0x0", 50, 0, True) | |
| self.assertEqual(mem_response["success"], False) | |
| # Malformed memory reference. | |
| mem_response = self.writeMemory("12345", 50, 0, True) | |
| self.assertEqual(mem_response["success"], False) | |
| ptr_deref = self.dap_server.request_evaluate("nonWritable")["body"] | |
| memref = ptr_deref["memoryReference"] | |
| # Writing to non-writable region should return an appropriate error. | |
| mem_response = self.writeMemory(memref, 50, 0, False) | |
| self.assertEqual(mem_response["success"], False) | |
| self.assertRegex( | |
| mem_response["body"]["error"]["format"], | |
| r"Memory " + memref + " region is not writable", | |
| ) | |
| # Trying to write empty value; data="" | |
| mem_response = self.writeMemory(memref) | |
| self.assertEqual(mem_response["success"], False) | |
| self.assertRegex( | |
| mem_response["body"]["error"]["format"], | |
| r"Data cannot be empty value. Provide valid data", | |
| ) | |
| # Verify that large memory writes fail if the range spans non-writable | |
| # or non -contiguous regions. | |
| data = bytes([0xFF] * 8192) | |
| mem_response = self.writeMemory( | |
| memref, int.from_bytes(data, byteorder="little"), 0, False | |
| ) | |
| self.assertEqual(mem_response["success"], False) | |
| self.assertRegex( | |
| mem_response["body"]["error"]["format"], | |
| r"Memory " + memref + " region is not writable", | |
| ) |