@@ -123,18 +123,55 @@ bool MemoryCache::RemoveInvalidRange(lldb::addr_t base_addr,
123
123
return false ;
124
124
}
125
125
126
+ lldb::DataBufferSP MemoryCache::GetL2CacheLine (lldb::addr_t line_base_addr,
127
+ Status &error) {
128
+ // This function assumes that the address given is aligned correctly.
129
+ assert ((line_base_addr % m_L2_cache_line_byte_size) == 0 );
130
+
131
+ std::lock_guard<std::recursive_mutex> guard (m_mutex);
132
+ auto pos = m_L2_cache.find (line_base_addr);
133
+ if (pos != m_L2_cache.end ())
134
+ return pos->second ;
135
+
136
+ auto data_buffer_heap_sp =
137
+ std::make_shared<DataBufferHeap>(m_L2_cache_line_byte_size, 0 );
138
+ size_t process_bytes_read = m_process.ReadMemoryFromInferior (
139
+ line_base_addr, data_buffer_heap_sp->GetBytes (),
140
+ data_buffer_heap_sp->GetByteSize (), error);
141
+
142
+ // If we failed a read, not much we can do.
143
+ if (process_bytes_read == 0 )
144
+ return lldb::DataBufferSP ();
145
+
146
+ // If we didn't get a complete read, we can still cache what we did get.
147
+ if (process_bytes_read < m_L2_cache_line_byte_size)
148
+ data_buffer_heap_sp->SetByteSize (process_bytes_read);
149
+
150
+ m_L2_cache[line_base_addr] = data_buffer_heap_sp;
151
+ return data_buffer_heap_sp;
152
+ }
153
+
126
154
size_t MemoryCache::Read (addr_t addr, void *dst, size_t dst_len,
127
155
Status &error) {
128
- size_t bytes_left = dst_len;
129
-
130
- // Check the L1 cache for a range that contain the entire memory read. If we
131
- // find a range in the L1 cache that does, we use it. Else we fall back to
132
- // reading memory in m_L2_cache_line_byte_size byte sized chunks. The L1
133
- // cache contains chunks of memory that are not required to be
134
- // m_L2_cache_line_byte_size bytes in size, so we don't try anything tricky
135
- // when reading from them (no partial reads from the L1 cache).
156
+ if (!dst || dst_len == 0 )
157
+ return 0 ;
136
158
137
159
std::lock_guard<std::recursive_mutex> guard (m_mutex);
160
+ // FIXME: We should do a more thorough check to make sure that we're not
161
+ // overlapping with any invalid ranges (e.g. Read 0x100 - 0x200 but there's an
162
+ // invalid range 0x180 - 0x280). `FindEntryThatContains` has an implementation
163
+ // that takes a range, but it only checks to see if the argument is contained
164
+ // by an existing invalid range. It cannot check if the argument contains
165
+ // invalid ranges and cannot check for overlaps.
166
+ if (m_invalid_ranges.FindEntryThatContains (addr)) {
167
+ error.SetErrorStringWithFormat (" memory read failed for 0x%" PRIx64, addr);
168
+ return 0 ;
169
+ }
170
+
171
+ // Check the L1 cache for a range that contains the entire memory read.
172
+ // L1 cache contains chunks of memory that are not required to be the size of
173
+ // an L2 cache line. We avoid trying to do partial reads from the L1 cache to
174
+ // simplify the implementation.
138
175
if (!m_L1_cache.empty ()) {
139
176
AddrRange read_range (addr, dst_len);
140
177
BlockMap::iterator pos = m_L1_cache.upper_bound (addr);
@@ -149,105 +186,82 @@ size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len,
149
186
}
150
187
}
151
188
152
- // If this memory read request is larger than the cache line size, then we
153
- // (1) try to read as much of it at once as possible, and (2) don't add the
154
- // data to the memory cache. We don't want to split a big read up into more
155
- // separate reads than necessary, and with a large memory read request, it is
156
- // unlikely that the caller function will ask for the next
157
- // 4 bytes after the large memory read - so there's little benefit to saving
158
- // it in the cache.
159
- if (dst && dst_len > m_L2_cache_line_byte_size) {
189
+ // If the size of the read is greater than the size of an L2 cache line, we'll
190
+ // just read from the inferior. If that read is successful, we'll cache what
191
+ // we read in the L1 cache for future use.
192
+ if (dst_len > m_L2_cache_line_byte_size) {
160
193
size_t bytes_read =
161
194
m_process.ReadMemoryFromInferior (addr, dst, dst_len, error);
162
- // Add this non block sized range to the L1 cache if we actually read
163
- // anything
164
195
if (bytes_read > 0 )
165
196
AddL1CacheData (addr, dst, bytes_read);
166
197
return bytes_read;
167
198
}
168
199
169
- if (dst && bytes_left > 0 ) {
170
- const uint32_t cache_line_byte_size = m_L2_cache_line_byte_size;
171
- uint8_t *dst_buf = (uint8_t *)dst;
172
- addr_t curr_addr = addr - (addr % cache_line_byte_size);
173
- addr_t cache_offset = addr - curr_addr;
174
-
175
- while (bytes_left > 0 ) {
176
- if (m_invalid_ranges.FindEntryThatContains (curr_addr)) {
177
- error.SetErrorStringWithFormat (" memory read failed for 0x%" PRIx64,
178
- curr_addr);
179
- return dst_len - bytes_left;
180
- }
181
-
182
- BlockMap::const_iterator pos = m_L2_cache.find (curr_addr);
183
- BlockMap::const_iterator end = m_L2_cache.end ();
184
-
185
- if (pos != end) {
186
- size_t curr_read_size = cache_line_byte_size - cache_offset;
187
- if (curr_read_size > bytes_left)
188
- curr_read_size = bytes_left;
189
-
190
- memcpy (dst_buf + dst_len - bytes_left,
191
- pos->second ->GetBytes () + cache_offset, curr_read_size);
192
-
193
- bytes_left -= curr_read_size;
194
- curr_addr += curr_read_size + cache_offset;
195
- cache_offset = 0 ;
196
-
197
- if (bytes_left > 0 ) {
198
- // Get sequential cache page hits
199
- for (++pos; (pos != end) && (bytes_left > 0 ); ++pos) {
200
- assert ((curr_addr % cache_line_byte_size) == 0 );
201
-
202
- if (pos->first != curr_addr)
203
- break ;
204
-
205
- curr_read_size = pos->second ->GetByteSize ();
206
- if (curr_read_size > bytes_left)
207
- curr_read_size = bytes_left;
200
+ // If the size of the read fits inside one L2 cache line, we'll try reading
201
+ // from the L2 cache. Note that if the range of memory we're reading sits
202
+ // between two contiguous cache lines, we'll touch two cache lines instead of
203
+ // just one.
204
+
205
+ // We're going to have all of our loads and reads be cache line aligned.
206
+ addr_t cache_line_offset = addr % m_L2_cache_line_byte_size;
207
+ addr_t cache_line_base_addr = addr - cache_line_offset;
208
+ DataBufferSP first_cache_line = GetL2CacheLine (cache_line_base_addr, error);
209
+ // If we get nothing, then the read to the inferior likely failed. Nothing to
210
+ // do here.
211
+ if (!first_cache_line)
212
+ return 0 ;
213
+
214
+ // If the cache line was not filled out completely and the offset is greater
215
+ // than what we have available, we can't do anything further here.
216
+ if (cache_line_offset >= first_cache_line->GetByteSize ())
217
+ return 0 ;
218
+
219
+ uint8_t *dst_buf = (uint8_t *)dst;
220
+ size_t bytes_left = dst_len;
221
+ size_t read_size = first_cache_line->GetByteSize () - cache_line_offset;
222
+ if (read_size > bytes_left)
223
+ read_size = bytes_left;
224
+
225
+ memcpy (dst_buf + dst_len - bytes_left,
226
+ first_cache_line->GetBytes () + cache_line_offset, read_size);
227
+ bytes_left -= read_size;
228
+
229
+ // If the cache line was not filled out completely and we still have data to
230
+ // read, we can't do anything further.
231
+ if (first_cache_line->GetByteSize () < m_L2_cache_line_byte_size &&
232
+ bytes_left > 0 )
233
+ return dst_len - bytes_left;
234
+
235
+ // We'll hit this scenario if our read straddles two cache lines.
236
+ if (bytes_left > 0 ) {
237
+ cache_line_base_addr += m_L2_cache_line_byte_size;
238
+
239
+ // FIXME: Until we are able to more thoroughly check for invalid ranges, we
240
+ // will have to check the second line to see if it is in an invalid range as
241
+ // well. See the check near the beginning of the function for more details.
242
+ if (m_invalid_ranges.FindEntryThatContains (cache_line_base_addr)) {
243
+ error.SetErrorStringWithFormat (" memory read failed for 0x%" PRIx64,
244
+ cache_line_base_addr);
245
+ return dst_len - bytes_left;
246
+ }
208
247
209
- memcpy (dst_buf + dst_len - bytes_left, pos->second ->GetBytes (),
210
- curr_read_size);
248
+ DataBufferSP second_cache_line =
249
+ GetL2CacheLine (cache_line_base_addr, error);
250
+ if (!second_cache_line)
251
+ return dst_len - bytes_left;
211
252
212
- bytes_left -= curr_read_size;
213
- curr_addr += curr_read_size;
253
+ read_size = bytes_left;
254
+ if (read_size > second_cache_line->GetByteSize ())
255
+ read_size = second_cache_line->GetByteSize ();
214
256
215
- // We have a cache page that succeeded to read some bytes but not
216
- // an entire page. If this happens, we must cap off how much data
217
- // we are able to read...
218
- if (pos->second ->GetByteSize () != cache_line_byte_size)
219
- return dst_len - bytes_left;
220
- }
221
- }
222
- }
257
+ memcpy (dst_buf + dst_len - bytes_left, second_cache_line->GetBytes (),
258
+ read_size);
259
+ bytes_left -= read_size;
223
260
224
- // We need to read from the process
225
-
226
- if (bytes_left > 0 ) {
227
- assert ((curr_addr % cache_line_byte_size) == 0 );
228
- std::unique_ptr<DataBufferHeap> data_buffer_heap_up (
229
- new DataBufferHeap (cache_line_byte_size, 0 ));
230
- size_t process_bytes_read = m_process.ReadMemoryFromInferior (
231
- curr_addr, data_buffer_heap_up->GetBytes (),
232
- data_buffer_heap_up->GetByteSize (), error);
233
- if (process_bytes_read == 0 )
234
- return dst_len - bytes_left;
235
-
236
- if (process_bytes_read != cache_line_byte_size) {
237
- data_buffer_heap_up->SetByteSize (process_bytes_read);
238
- if (process_bytes_read < data_buffer_heap_up->GetByteSize ()) {
239
- dst_len -= data_buffer_heap_up->GetByteSize () - process_bytes_read;
240
- bytes_left = process_bytes_read;
241
- }
242
- }
243
- m_L2_cache[curr_addr] = DataBufferSP (data_buffer_heap_up.release ());
244
- // We have read data and put it into the cache, continue through the
245
- // loop again to get the data out of the cache...
246
- }
247
- }
261
+ return dst_len - bytes_left;
248
262
}
249
263
250
- return dst_len - bytes_left ;
264
+ return dst_len;
251
265
}
252
266
253
267
AllocatedBlock::AllocatedBlock (lldb::addr_t addr, uint32_t byte_size,
0 commit comments