| 1 | //===-- MemoryTest.cpp ----------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Target/Memory.h" |
| 10 | #include "Plugins/Platform/MacOSX/PlatformMacOSX.h" |
| 11 | #include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h" |
| 12 | #include "lldb/Core/Debugger.h" |
| 13 | #include "lldb/Host/FileSystem.h" |
| 14 | #include "lldb/Host/HostInfo.h" |
| 15 | #include "lldb/Target/Process.h" |
| 16 | #include "lldb/Target/Target.h" |
| 17 | #include "lldb/Utility/ArchSpec.h" |
| 18 | #include "lldb/Utility/DataBufferHeap.h" |
| 19 | #include "gtest/gtest.h" |
| 20 | |
| 21 | using namespace lldb_private; |
| 22 | using namespace lldb; |
| 23 | |
| 24 | namespace { |
| 25 | class MemoryTest : public ::testing::Test { |
| 26 | public: |
| 27 | void SetUp() override { |
| 28 | FileSystem::Initialize(); |
| 29 | HostInfo::Initialize(); |
| 30 | PlatformMacOSX::Initialize(); |
| 31 | } |
| 32 | void TearDown() override { |
| 33 | PlatformMacOSX::Terminate(); |
| 34 | HostInfo::Terminate(); |
| 35 | FileSystem::Terminate(); |
| 36 | } |
| 37 | }; |
| 38 | |
| 39 | class DummyProcess : public Process { |
| 40 | public: |
| 41 | DummyProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp) |
| 42 | : Process(target_sp, listener_sp), m_bytes_left(0) {} |
| 43 | |
| 44 | // Required overrides |
| 45 | bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override { |
| 46 | return true; |
| 47 | } |
| 48 | Status DoDestroy() override { return {}; } |
| 49 | void RefreshStateAfterStop() override {} |
| 50 | size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, |
| 51 | Status &error) override { |
| 52 | if (m_bytes_left == 0) |
| 53 | return 0; |
| 54 | |
| 55 | size_t num_bytes_to_write = size; |
| 56 | if (m_bytes_left < size) { |
| 57 | num_bytes_to_write = m_bytes_left; |
| 58 | m_bytes_left = 0; |
| 59 | } else { |
| 60 | m_bytes_left -= size; |
| 61 | } |
| 62 | |
| 63 | memset(s: buf, c: 'B', n: num_bytes_to_write); |
| 64 | return num_bytes_to_write; |
| 65 | } |
| 66 | bool DoUpdateThreadList(ThreadList &old_thread_list, |
| 67 | ThreadList &new_thread_list) override { |
| 68 | return false; |
| 69 | } |
| 70 | llvm::StringRef GetPluginName() override { return "Dummy" ; } |
| 71 | |
| 72 | // Test-specific additions |
| 73 | size_t m_bytes_left; |
| 74 | MemoryCache &GetMemoryCache() { return m_memory_cache; } |
| 75 | void SetMaxReadSize(size_t size) { m_bytes_left = size; } |
| 76 | }; |
| 77 | } // namespace |
| 78 | |
| 79 | TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) { |
| 80 | PlatformSP platform_sp; |
| 81 | TargetSP target_sp; |
| 82 | debugger_sp->GetTargetList().CreateTarget( |
| 83 | debugger&: *debugger_sp, user_exe_path: "" , arch, get_dependent_modules: eLoadDependentsNo, platform_sp, target_sp); |
| 84 | return target_sp; |
| 85 | } |
| 86 | |
| 87 | TEST_F(MemoryTest, TesetMemoryCacheRead) { |
| 88 | ArchSpec arch("x86_64-apple-macosx-" ); |
| 89 | |
| 90 | Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(force: true, arch: &arch)); |
| 91 | |
| 92 | DebuggerSP debugger_sp = Debugger::CreateInstance(); |
| 93 | ASSERT_TRUE(debugger_sp); |
| 94 | |
| 95 | TargetSP target_sp = CreateTarget(debugger_sp, arch); |
| 96 | ASSERT_TRUE(target_sp); |
| 97 | |
| 98 | ListenerSP listener_sp(Listener::MakeListener(name: "dummy" )); |
| 99 | ProcessSP process_sp = std::make_shared<DummyProcess>(args&: target_sp, args&: listener_sp); |
| 100 | ASSERT_TRUE(process_sp); |
| 101 | |
| 102 | DummyProcess *process = static_cast<DummyProcess *>(process_sp.get()); |
| 103 | MemoryCache &mem_cache = process->GetMemoryCache(); |
| 104 | const uint64_t l2_cache_size = process->GetMemoryCacheLineSize(); |
| 105 | Status error; |
| 106 | auto data_sp = std::make_shared<DataBufferHeap>(args: l2_cache_size * 2, args: '\0'); |
| 107 | size_t bytes_read = 0; |
| 108 | |
| 109 | // Cache empty, memory read fails, size > l2 cache size |
| 110 | process->SetMaxReadSize(0); |
| 111 | bytes_read = mem_cache.Read(addr: 0x1000, dst: data_sp->GetBytes(), |
| 112 | dst_len: data_sp->GetByteSize(), error); |
| 113 | ASSERT_TRUE(bytes_read == 0); |
| 114 | |
| 115 | // Cache empty, memory read fails, size <= l2 cache size |
| 116 | data_sp->SetByteSize(l2_cache_size); |
| 117 | bytes_read = mem_cache.Read(addr: 0x1000, dst: data_sp->GetBytes(), |
| 118 | dst_len: data_sp->GetByteSize(), error); |
| 119 | ASSERT_TRUE(bytes_read == 0); |
| 120 | |
| 121 | // Cache empty, memory read succeeds, size > l2 cache size |
| 122 | process->SetMaxReadSize(l2_cache_size * 4); |
| 123 | data_sp->SetByteSize(l2_cache_size * 2); |
| 124 | bytes_read = mem_cache.Read(addr: 0x1000, dst: data_sp->GetBytes(), |
| 125 | dst_len: data_sp->GetByteSize(), error); |
| 126 | ASSERT_TRUE(bytes_read == data_sp->GetByteSize()); |
| 127 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size * 2); |
| 128 | |
| 129 | // Reading data previously cached (not in L2 cache). |
| 130 | data_sp->SetByteSize(l2_cache_size + 1); |
| 131 | bytes_read = mem_cache.Read(addr: 0x1000, dst: data_sp->GetBytes(), |
| 132 | dst_len: data_sp->GetByteSize(), error); |
| 133 | ASSERT_TRUE(bytes_read == data_sp->GetByteSize()); |
| 134 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size * 2); // Verify we didn't |
| 135 | // read from the |
| 136 | // inferior. |
| 137 | |
| 138 | // Read from a different address, but make the size == l2 cache size. |
| 139 | // This should fill in a the L2 cache. |
| 140 | data_sp->SetByteSize(l2_cache_size); |
| 141 | bytes_read = mem_cache.Read(addr: 0x2000, dst: data_sp->GetBytes(), |
| 142 | dst_len: data_sp->GetByteSize(), error); |
| 143 | ASSERT_TRUE(bytes_read == data_sp->GetByteSize()); |
| 144 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size); |
| 145 | |
| 146 | // Read from that L2 cache entry but read less than size of the cache line. |
| 147 | // Additionally, read from an offset. |
| 148 | data_sp->SetByteSize(l2_cache_size - 5); |
| 149 | bytes_read = mem_cache.Read(addr: 0x2001, dst: data_sp->GetBytes(), |
| 150 | dst_len: data_sp->GetByteSize(), error); |
| 151 | ASSERT_TRUE(bytes_read == data_sp->GetByteSize()); |
| 152 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size); // Verify we didn't read |
| 153 | // from the inferior. |
| 154 | |
| 155 | // What happens if we try to populate an L2 cache line but the read gives less |
| 156 | // than the size of a cache line? |
| 157 | process->SetMaxReadSize(l2_cache_size - 10); |
| 158 | data_sp->SetByteSize(l2_cache_size - 5); |
| 159 | bytes_read = mem_cache.Read(addr: 0x3000, dst: data_sp->GetBytes(), |
| 160 | dst_len: data_sp->GetByteSize(), error); |
| 161 | ASSERT_TRUE(bytes_read == l2_cache_size - 10); |
| 162 | ASSERT_TRUE(process->m_bytes_left == 0); |
| 163 | |
| 164 | // What happens if we have a partial L2 cache line filled in and we try to |
| 165 | // read the part that isn't filled in? |
| 166 | data_sp->SetByteSize(10); |
| 167 | bytes_read = mem_cache.Read(addr: 0x3000 + l2_cache_size - 10, dst: data_sp->GetBytes(), |
| 168 | dst_len: data_sp->GetByteSize(), error); |
| 169 | ASSERT_TRUE(bytes_read == 0); // The last 10 bytes from this line are |
| 170 | // missing and we should be reading nothing |
| 171 | // here. |
| 172 | |
| 173 | // What happens when we try to straddle 2 cache lines? |
| 174 | process->SetMaxReadSize(l2_cache_size * 2); |
| 175 | data_sp->SetByteSize(l2_cache_size); |
| 176 | bytes_read = mem_cache.Read(addr: 0x4001, dst: data_sp->GetBytes(), |
| 177 | dst_len: data_sp->GetByteSize(), error); |
| 178 | ASSERT_TRUE(bytes_read == l2_cache_size); |
| 179 | ASSERT_TRUE(process->m_bytes_left == 0); |
| 180 | |
| 181 | // What happens when we try to straddle 2 cache lines where the first one is |
| 182 | // only partially filled? |
| 183 | process->SetMaxReadSize(l2_cache_size - 1); |
| 184 | data_sp->SetByteSize(l2_cache_size); |
| 185 | bytes_read = mem_cache.Read(addr: 0x5005, dst: data_sp->GetBytes(), |
| 186 | dst_len: data_sp->GetByteSize(), error); |
| 187 | ASSERT_TRUE(bytes_read == l2_cache_size - 6); // Ignoring the first 5 bytes, |
| 188 | // missing the last byte |
| 189 | ASSERT_TRUE(process->m_bytes_left == 0); |
| 190 | |
| 191 | // What happens if we add an invalid range and try to do a read larger than |
| 192 | // a cache line? |
| 193 | mem_cache.AddInvalidRange(base_addr: 0x6000, byte_size: l2_cache_size * 2); |
| 194 | process->SetMaxReadSize(l2_cache_size * 2); |
| 195 | data_sp->SetByteSize(l2_cache_size * 2); |
| 196 | bytes_read = mem_cache.Read(addr: 0x6000, dst: data_sp->GetBytes(), |
| 197 | dst_len: data_sp->GetByteSize(), error); |
| 198 | ASSERT_TRUE(bytes_read == 0); |
| 199 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size * 2); |
| 200 | |
| 201 | // What happens if we add an invalid range and try to do a read lt/eq a |
| 202 | // cache line? |
| 203 | mem_cache.AddInvalidRange(base_addr: 0x7000, byte_size: l2_cache_size); |
| 204 | process->SetMaxReadSize(l2_cache_size); |
| 205 | data_sp->SetByteSize(l2_cache_size); |
| 206 | bytes_read = mem_cache.Read(addr: 0x7000, dst: data_sp->GetBytes(), |
| 207 | dst_len: data_sp->GetByteSize(), error); |
| 208 | ASSERT_TRUE(bytes_read == 0); |
| 209 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size); |
| 210 | |
| 211 | // What happens if we remove the invalid range and read again? |
| 212 | mem_cache.RemoveInvalidRange(base_addr: 0x7000, byte_size: l2_cache_size); |
| 213 | bytes_read = mem_cache.Read(addr: 0x7000, dst: data_sp->GetBytes(), |
| 214 | dst_len: data_sp->GetByteSize(), error); |
| 215 | ASSERT_TRUE(bytes_read == l2_cache_size); |
| 216 | ASSERT_TRUE(process->m_bytes_left == 0); |
| 217 | |
| 218 | // What happens if we flush and read again? |
| 219 | process->SetMaxReadSize(l2_cache_size * 2); |
| 220 | mem_cache.Flush(addr: 0x7000, size: l2_cache_size); |
| 221 | bytes_read = mem_cache.Read(addr: 0x7000, dst: data_sp->GetBytes(), |
| 222 | dst_len: data_sp->GetByteSize(), error); |
| 223 | ASSERT_TRUE(bytes_read == l2_cache_size); |
| 224 | ASSERT_TRUE(process->m_bytes_left == l2_cache_size); // Verify that we re-read |
| 225 | // instead of using an |
| 226 | // old cache |
| 227 | } |
| 228 | |