"""
Test that lldb removes non-address bits in situations where they would cause
failures if not removed. Like when reading memory. Tests are done at command
and API level because commands may remove non-address bits for display
reasons which can make it seem like the operation as a whole works but at the
API level it won't if we don't remove them there also.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class AArch64LinuxNonAddressBitMemoryAccessTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
def setup_test(self):
if not self.isAArch64PAuth():
self.skipTest("Target must support pointer authentication.")
self.build()
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
lldbutil.run_break_set_by_file_and_line(
self,
"main.c",
line_number("main.c", "// Set break point at this line."),
num_expected_locations=1,
)
self.runCmd("run", RUN_SUCCEEDED)
if self.process().GetState() == lldb.eStateExited:
self.fail("Test program failed to run.")
self.expect(
"thread list",
STOPPED_DUE_TO_BREAKPOINT,
substrs=["stopped", "stop reason = breakpoint"],
)
def check_cmd_read_write(self, write_to, read_from, data):
self.runCmd("memory write {} {}".format(write_to, data))
self.expect("memory read {}".format(read_from), substrs=[data])
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_non_address_bit_memory_read_write_cmds(self):
self.setup_test()
# Writes should be visible through either pointer
self.check_cmd_read_write("buf", "buf", "01 02 03 04")
self.check_cmd_read_write(
"buf_with_non_address", "buf_with_non_address", "02 03 04 05"
)
self.check_cmd_read_write("buf", "buf_with_non_address", "03 04 05 06")
self.check_cmd_read_write("buf_with_non_address", "buf", "04 05 06 07")
# Printing either should get the same result
self.expect("expression -f hex -- *(uint32_t*)buf", substrs=["0x07060504"])
self.expect(
"expression -f hex -- *(uint32_t*)buf_with_non_address",
substrs=["0x07060504"],
)
def get_ptr_values(self):
frame = self.process().GetThreadAtIndex(0).GetFrameAtIndex(0)
buf = frame.FindVariable("buf").GetValueAsUnsigned()
buf_with_non_address = frame.FindVariable(
"buf_with_non_address"
).GetValueAsUnsigned()
return buf, buf_with_non_address
def check_api_read_write(self, write_to, read_from, data):
error = lldb.SBError()
written = self.process().WriteMemory(write_to, data, error)
self.assertTrue(error.Success())
self.assertEqual(len(data), written)
buf_content = self.process().ReadMemory(read_from, 4, error)
self.assertTrue(error.Success())
self.assertEqual(data, buf_content)
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_non_address_bit_memory_read_write_api_process(self):
self.setup_test()
buf, buf_with_non_address = self.get_ptr_values()
# Writes are visible through either pointer
self.check_api_read_write(buf, buf, bytes([0, 1, 2, 3]))
self.check_api_read_write(
buf_with_non_address, buf_with_non_address, bytes([1, 2, 3, 4])
)
self.check_api_read_write(buf, buf_with_non_address, bytes([2, 3, 4, 5]))
self.check_api_read_write(buf_with_non_address, buf, bytes([3, 4, 5, 6]))
# Now check all the "Read<type>FromMemory" don't fail
error = lldb.SBError()
# Last 4 bytes are just for the pointer read
data = bytes([0x4C, 0x4C, 0x44, 0x42, 0x00, 0x12, 0x34, 0x56])
written = self.process().WriteMemory(buf, data, error)
self.assertTrue(error.Success())
self.assertEqual(len(data), written)
# C string
c_string = self.process().ReadCStringFromMemory(buf_with_non_address, 5, error)
self.assertTrue(error.Success())
self.assertEqual("LLDB", c_string)
# Unsigned
unsigned_num = self.process().ReadUnsignedFromMemory(
buf_with_non_address, 4, error
)
self.assertTrue(error.Success())
self.assertEqual(0x42444C4C, unsigned_num)
# Pointer
ptr = self.process().ReadPointerFromMemory(buf_with_non_address, error)
self.assertTrue(error.Success())
self.assertEqual(0x5634120042444C4C, ptr)
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_non_address_bit_memory_read_write_api_target(self):
self.setup_test()
buf, buf_with_non_address = self.get_ptr_values()
# Target only has ReadMemory
error = lldb.SBError()
data = bytes([1, 2, 3, 4])
written = self.process().WriteMemory(buf, data, error)
self.assertTrue(error.Success())
self.assertEqual(len(data), written)
addr = lldb.SBAddress()
addr.SetLoadAddress(buf, self.target())
buf_read = self.target().ReadMemory(addr, 4, error)
self.assertTrue(error.Success())
self.assertEqual(data, buf_read)
addr.SetLoadAddress(buf_with_non_address, self.target())
buf_non_address_read = self.target().ReadMemory(addr, 4, error)
self.assertTrue(error.Success())
self.assertEqual(data, buf_non_address_read)
# Read<type>FromMemory are in Target but not SBTarget so no tests for those.
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_non_address_bit_memory_caching(self):
# The read/write tests above do exercise the cache but this test
# only cares that the cache sees buf and buf_with_non_address
# as the same location.
self.setup_test()
buf, buf_with_non_address = self.get_ptr_values()
# Enable packet logging so we can see when reads actually
# happen.
log_file = self.getBuildArtifact("lldb-non-address-bit-log.txt")
# This defaults to overwriting the file so we don't need to delete
# any existing files.
self.runCmd("log enable gdb-remote packets -f '%s'" % log_file)
# This should fill the cache by doing a read of buf_with_non_address
# with the non-address bits removed (which is == buf).
self.runCmd("expression buf_with_non_address")
# This will read from the cache since the two pointers point to the
# same place.
self.runCmd("expression buf")
# Open log ignoring utf-8 decode errors
with open(log_file, "r", errors="ignore") as f:
read_packet = "send packet: $x{:x}"
# Since we allocated a 4k page that page will be aligned to 4k, which
# also fits the 512 byte line size of the memory cache. So we can expect
# to find a packet with its exact address.
read_buf_packet = read_packet.format(buf)
read_buf_with_non_address_packet = read_packet.format(buf_with_non_address)
# We expect to find 1 and only 1 read of buf.
# We expect to find no reads using buf_with_no_address.
found_read_buf = False
for line in f:
if read_buf_packet in line:
if found_read_buf:
self.fail("Expected 1 read of buf but found more than one.")
found_read_buf = True
if read_buf_with_non_address_packet in line:
self.fail("Unexpected read of buf_with_non_address found.")
if not found_read_buf:
self.fail("Did not find any reads of buf.")
@skipIfLLVMTargetMissing("AArch64")
def test_non_address_bit_memory_corefile(self):
self.runCmd("target create --core corefile")
self.expect("thread list", substrs=["stopped", "stop reason = signal SIGSEGV"])
# No caching (the program/corefile are the cache) and no writing
# to memory. So just check that tagged/untagged addresses read
# the same location.
# These are known addresses in the corefile, since we won't have symbols.
buf = 0x0000FFFFA75A5000
buf_with_non_address = 0xFF0BFFFFA75A5000
expected = ["4c 4c 44 42", "LLDB"]
self.expect("memory read 0x{:x}".format(buf), substrs=expected)
self.expect("memory read 0x{:x}".format(buf_with_non_address), substrs=expected)
# This takes a more direct route to ReadMemory. As opposed to "memory read"
# above that might fix the addresses up front for display reasons.
self.expect("expression (char*)0x{:x}".format(buf), substrs=["LLDB"])
self.expect(
"expression (char*)0x{:x}".format(buf_with_non_address), substrs=["LLDB"]
)
def check_reads(addrs, method, num_bytes, expected):
error = lldb.SBError()
for addr in addrs:
if num_bytes is None:
got = method(addr, error)
else:
got = method(addr, num_bytes, error)
self.assertTrue(error.Success())
self.assertEqual(expected, got)
addr_buf = lldb.SBAddress()
addr_buf.SetLoadAddress(buf, self.target())
addr_buf_with_non_address = lldb.SBAddress()
addr_buf_with_non_address.SetLoadAddress(buf_with_non_address, self.target())
check_reads(
[addr_buf, addr_buf_with_non_address], self.target().ReadMemory, 4, b"LLDB"
)
addrs = [buf, buf_with_non_address]
check_reads(addrs, self.process().ReadMemory, 4, b"LLDB")
check_reads(addrs, self.process().ReadCStringFromMemory, 5, "LLDB")
check_reads(addrs, self.process().ReadUnsignedFromMemory, 4, 0x42444C4C)
check_reads(
addrs, self.process().ReadPointerFromMemory, None, 0x0000000042444C4C
)