| """ | |
| Test the 'memory read' command. | |
| """ | |
| import lldb | |
| import lldbsuite.test.lldbutil as lldbutil | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| class MemoryReadTestCase(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| def build_run_stop(self): | |
| self.build() | |
| lldbutil.run_to_source_breakpoint( | |
| self, "// break here", lldb.SBFileSpec("main.c") | |
| ) | |
| def test_memory_read_c_string(self): | |
| """Test that reading memory as a c string respects the size limit given | |
| and warns if the null terminator is missing.""" | |
| self.build_run_stop() | |
| # The size here is the size in memory so it includes the null terminator. | |
| cmd = 'memory read --format "c-string" --size {} &my_string' | |
| # Size matches the size of the array. | |
| self.expect(cmd.format(8), substrs=['"abcdefg"']) | |
| # If size would take us past the terminator we stop at the terminator. | |
| self.expect(cmd.format(10), substrs=['"abcdefg"']) | |
| # Size 3 means 2 chars and a terminator. So we print 2 chars but warn because | |
| # the third isn't 0 as expected. | |
| self.expect(cmd.format(3), substrs=['"ab"']) | |
| self.assertRegex( | |
| self.res.GetError(), | |
| "unable to find a null-terminated string at 0x[0-9A-Fa-f]+" | |
| ". Consider increasing the maximum read length", | |
| ) | |
| @skipIf(archs=no_match("^(riscv|aarch64).*")) | |
| def test_memory_read_instruction_decode_failure(self): | |
| """Test the 'memory read' command with instruction format.""" | |
| self.build_run_stop() | |
| # assume that 0xffffff is invalid instruction in RISC-V and AArch64, | |
| # so decoding it will fail | |
| self.expect( | |
| "memory read --format instruction `&my_insns[0]` `&my_insns[3]`", | |
| substrs=["failed to decode instructions at"], | |
| ) | |
| def test_memory_read(self): | |
| """Test the 'memory read' command with plain and vector formats.""" | |
| self.build_run_stop() | |
| # (lldb) memory read -f d -c 1 `&argc` | |
| # 0x7fff5fbff9a0: 1 | |
| self.runCmd("memory read -f d -c 1 `&argc`") | |
| # Find the starting address for variable 'argc' to verify later that the | |
| # '--format uint32_t[] --size 4 --count 4' option increments the address | |
| # correctly. | |
| line = self.res.GetOutput().splitlines()[0] | |
| items = line.split(":") | |
| address = int(items[0], 0) | |
| argc = int(items[1], 0) | |
| self.assertGreater(address, 0) | |
| self.assertEqual(argc, 1) | |
| # (lldb) memory read --format uint32_t[] --size 4 --count 4 `&argc` | |
| # 0x7fff5fbff9a0: {0x00000001} | |
| # 0x7fff5fbff9a4: {0x00000000} | |
| # 0x7fff5fbff9a8: {0x0ec0bf27} | |
| # 0x7fff5fbff9ac: {0x215db505} | |
| self.runCmd("memory read --format uint32_t[] --size 4 --count 4 `&argc`") | |
| lines = self.res.GetOutput().splitlines() | |
| for i in range(4): | |
| if i == 0: | |
| # Verify that the printout for argc is correct. | |
| self.assertEqual(argc, int(lines[i].split(":")[1].strip(" {}"), 0)) | |
| addr = int(lines[i].split(":")[0], 0) | |
| # Verify that the printout for addr is incremented correctly. | |
| self.assertEqual(addr, (address + i * 4)) | |
| # (lldb) memory read --format char[] --size 7 --count 1 `&my_string` | |
| # 0x7fff5fbff990: {abcdefg} | |
| self.expect( | |
| "memory read --format char[] --size 7 --count 1 `&my_string`", | |
| substrs=["abcdefg"], | |
| ) | |
| # (lldb) memory read --format 'hex float' --size 16 `&argc` | |
| # 0x7fff5fbff5b0: error: unsupported byte size (16) for hex float | |
| # format | |
| self.expect( | |
| "memory read --format 'hex float' --size 16 `&argc`", | |
| substrs=["unsupported byte size (16) for hex float format"], | |
| ) | |
| self.expect( | |
| "memory read --format 'float' --count 1 --size 8 `&my_double`", | |
| substrs=["1234."], | |
| ) | |
| # (lldb) memory read --format 'float' --count 1 --size 20 `&my_double` | |
| # 0x7fff5fbff598: error: unsupported byte size (20) for float format | |
| self.expect( | |
| "memory read --format 'float' --count 1 --size 20 `&my_double`", | |
| substrs=["unsupported byte size (20) for float format"], | |
| ) | |
| self.expect( | |
| "memory read --type int --count 5 `&my_ints[0]`", | |
| substrs=["(int) 0x", "2", "4", "6", "8", "10"], | |
| ) | |
| self.expect( | |
| "memory read --type int --count 5 --format hex `&my_ints[0]`", | |
| substrs=["(int) 0x", "0x", "0a"], | |
| ) | |
| self.expect( | |
| "memory read --type int --count 5 --offset 5 `&my_ints[0]`", | |
| substrs=["(int) 0x", "12", "14", "16", "18", "20"], | |
| ) | |
| # the gdb format specifier and the size in characters for | |
| # the returned values including the 0x prefix. | |
| variations = [["b", 4], ["h", 6], ["w", 10], ["g", 18]] | |
| for v in variations: | |
| formatter = v[0] | |
| expected_object_length = v[1] | |
| self.runCmd("memory read --gdb-format 4%s &my_uint64s" % formatter) | |
| lines = self.res.GetOutput().splitlines() | |
| objects_read = [] | |
| for l in lines: | |
| objects_read.extend(l.split(":")[1].split()) | |
| # Check that we got back 4 0x0000 etc bytes | |
| for o in objects_read: | |
| self.assertEqual(len(o), expected_object_length) | |
| self.assertEqual(len(objects_read), 4) | |
| def test_memory_read_array_name_decays_to_address(self): | |
| """An array expression decays to a pointer to its first element, so | |
| `memory read <array>` and `memory read `<array>`` must behave like | |
| `memory read &<array>`, rather than failing or reading a bogus address.""" | |
| self.build_run_stop() | |
| fmt = "memory read --format uint32_t[] --size 4 --count 3 " | |
| # Parses the output of the command above, which looks like: | |
| # 0x7fff5fbff990: {0x00000002} | |
| # 0x7fff5fbff994: {0x00000004} | |
| # 0x7fff5fbff998: {0x00000006} | |
| # i.e. "<address>: {<value>}" per line. We return the address of the | |
| # first line and the list of values across all lines. | |
| def read_addr_and_values(suffix): | |
| self.runCmd(fmt + suffix) | |
| lines = self.res.GetOutput().splitlines() | |
| addr = int(lines[0].split(":")[0], 0) | |
| values = [] | |
| for line in lines: | |
| values.extend(v.strip(" {}") for v in line.split(":")[1].split()) | |
| return addr, values | |
| # Taking the address explicitly yields a pointer whose value is the | |
| # array's address; this is the reference every other form must match. | |
| ref_addr, ref_values = read_addr_and_values("&my_ints") | |
| self.assertEqual(ref_values, ["0x00000002", "0x00000004", "0x00000006"]) | |
| # Every one of these forms must resolve to the same address and data: | |
| # &my_ints pointer, via OptionArgParser::ToAddress | |
| # `&my_ints` pointer, via CommandInterpreter::PreprocessToken | |
| # my_ints array decay, via OptionArgParser::ToAddress | |
| # `my_ints` array decay, via CommandInterpreter::PreprocessToken | |
| for suffix in ["&my_ints", "`&my_ints`", "my_ints", "`my_ints`"]: | |
| addr, values = read_addr_and_values(suffix) | |
| self.assertEqual( | |
| addr, ref_addr, "wrong address for 'memory read %s'" % suffix | |
| ) | |
| self.assertEqual( | |
| values, ref_values, "wrong data for 'memory read %s'" % suffix | |
| ) | |
| def test_memory_read_file(self): | |
| self.build_run_stop() | |
| res = lldb.SBCommandReturnObject() | |
| self.ci.HandleCommand("memory read -f d -c 1 `&argc`", res) | |
| self.assertTrue(res.Succeeded(), "memory read failed:" + res.GetError()) | |
| # Record golden output. | |
| golden_output = res.GetOutput() | |
| memory_read_file = self.getBuildArtifact("memory-read-output") | |
| def check_file_content(expected): | |
| with open(memory_read_file) as f: | |
| lines = f.readlines() | |
| lines = [s.strip() for s in lines] | |
| expected = [s.strip() for s in expected] | |
| self.assertEqual(lines, expected) | |
| # Sanity check. | |
| self.runCmd("memory read -f d -c 1 -o '{}' `&argc`".format(memory_read_file)) | |
| check_file_content([golden_output]) | |
| # Write some garbage to the file. | |
| with open(memory_read_file, "w") as f: | |
| f.write("some garbage") | |
| # Make sure the file is truncated when we run the command again. | |
| self.runCmd("memory read -f d -c 1 -o '{}' `&argc`".format(memory_read_file)) | |
| check_file_content([golden_output]) | |
| # Make sure the file is appended when we run the command with --append-outfile. | |
| self.runCmd( | |
| "memory read -f d -c 1 -o '{}' --append-outfile `&argc`".format( | |
| memory_read_file | |
| ) | |
| ) | |
| check_file_content([golden_output, golden_output]) | |
| def test_memory_read_repeat(self): | |
| self.build_run_stop() | |
| def expected_bytes(start, end): | |
| # Generates ": <2 digit hex> <2 digit hex> <repeats><double space>". | |
| return ": " + " ".join([f"{n:02x}" for n in range(start, end)]) + " " | |
| # First read gives us the first half of the array. | |
| self.expect( | |
| "memory read &incrementing_bytes", | |
| substrs=[expected_bytes(0x0, 0x10), expected_bytes(0x10, 0x20)], | |
| ) | |
| # Repeating the command sets the start address to the end address of the | |
| # previous use. | |
| self.expect( | |
| "memory read", | |
| substrs=[expected_bytes(0x20, 0x30), expected_bytes(0x30, 0x40)], | |
| ) | |
| # Read first half of array as characters. | |
| self.expect( | |
| 'memory read --format "character" --count 32 &incrementing_bytes', | |
| substrs=[ | |
| r": \0\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f", | |
| ], | |
| ) | |
| # Repeated command should read the second half and retain the character | |
| # format. | |
| # FIXME: Options are not repeated, see https://github.com/llvm/llvm-project/issues/192057. | |
| # self.expect("memory read", substrs=[ | |
| # # Note that the second space is actually in the array. | |
| # ": !\"#$%&'()*+,-./0123456789:;<=>?" | |
| # ]) | |
| # Due to that bug, only the address is incremented, and the style is | |
| # reset to the default. | |
| self.expect( | |
| "memory read", | |
| substrs=[expected_bytes(0x20, 0x30), expected_bytes(0x30, 0x40)], | |
| ) |