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