1use memory_mapped_vmo::{ReadOnlyMemoryMappedVmo, ReadWriteMemoryMappedVmo};
6use std::alloc::Layout;
7use std::mem::{align_of, size_of, size_of_val};
8use zerocopy::FromBytes;
9
10type Offset = u32;
12const OFFSET_INVALID: Offset = Offset::MAX;
13
14type StackTraceLength = u16;
17
18const NUM_STACK_BUCKETS: usize = 1 << 13;
24type StackBucketHeads = [Offset; NUM_STACK_BUCKETS];
25
26const CRC32_TABLE: crc::Crc<u32> = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
27
28#[derive(Clone, Copy, Debug, Eq, FromBytes, Hash, Ord, PartialEq, PartialOrd)]
30#[repr(transparent)]
31pub struct ResourceKey(Offset);
32
33impl ResourceKey {
34 #[cfg(test)]
36 pub(crate) const fn from_raw(offset: Offset) -> ResourceKey {
37 ResourceKey(offset)
38 }
39
40 pub const fn into_raw(self) -> Offset {
41 self.0
42 }
43}
44
45#[repr(C)]
46#[derive(Debug, FromBytes)]
47pub struct ThreadInfo {
48 pub koid: zx::sys::zx_koid_t,
49 pub name: zx::Name,
50}
51
52pub struct ResourcesTableWriter {
71 storage: ReadWriteMemoryMappedVmo,
72 watermark: usize, }
74
75impl ResourcesTableWriter {
76 pub unsafe fn new(vmo: &zx::Vmo) -> Result<ResourcesTableWriter, crate::Error> {
84 let storage = unsafe { ReadWriteMemoryMappedVmo::new(vmo)? };
85 if storage.vmo_size() < size_of::<StackBucketHeads>() {
86 return Err(crate::Error::BufferTooSmall);
87 } else if storage.vmo_size() - 1 > Offset::MAX as usize {
88 return Err(crate::Error::BufferTooBig);
89 }
90
91 let mut result = ResourcesTableWriter { storage, watermark: size_of::<StackBucketHeads>() };
92
93 for bucket_index in 0..NUM_STACK_BUCKETS {
95 *result.stack_bucket_head_at(bucket_index) = OFFSET_INVALID;
96 }
97
98 Ok(result)
99 }
100
101 fn allocate(&mut self, layout: Layout) -> Result<Offset, crate::Error> {
103 if layout.align() > zx::system_get_page_size() as usize {
106 return Err(crate::Error::InvalidInput);
107 }
108
109 let result_start = (self.watermark + layout.align() - 1) & !(layout.align() - 1);
110 let result_end = result_start + layout.size();
111
112 if result_end <= self.storage.vmo_size() {
113 self.watermark = result_end;
114 Ok(result_start as Offset)
115 } else {
116 Err(crate::Error::OutOfSpace)
117 }
118 }
119
120 fn compute_bucket_index(compressed_stack_trace: &[u8]) -> usize {
122 let crc = CRC32_TABLE.checksum(compressed_stack_trace);
123 crc as usize % NUM_STACK_BUCKETS
124 }
125
126 fn stack_bucket_head_at(&mut self, bucket_index: usize) -> &mut Offset {
128 let bucket_heads = self.storage.get_object_mut::<StackBucketHeads>(0).unwrap();
130 &mut bucket_heads[bucket_index]
131 }
132
133 fn find_in_bucket(
136 &mut self,
137 bucket_index: usize,
138 compressed_stack_trace: &[u8],
139 ) -> Option<Offset> {
140 let mut curr = *self.stack_bucket_head_at(bucket_index);
141 while curr != OFFSET_INVALID {
142 let curr_next: Offset = *self.storage.get_object(curr as usize).unwrap();
145 let payload_offset = curr as usize + size_of_val(&curr_next);
146 let curr_payload = get_compressed_stack_trace(&self.storage, payload_offset).unwrap();
147
148 if *curr_payload == *compressed_stack_trace {
150 return Some(curr);
151 }
152
153 curr = curr_next;
154 }
155
156 None
158 }
159
160 fn insert_in_bucket(
161 &mut self,
162 bucket_index: usize,
163 compressed_stack_trace: &[u8],
164 ) -> Result<Offset, crate::Error> {
165 let alloc_bytes =
170 size_of::<Offset>() + size_of::<StackTraceLength>() + compressed_stack_trace.len();
171 let alloc_align = align_of::<Offset>();
172 let new = self.allocate(Layout::from_size_align(alloc_bytes, alloc_align).unwrap())?;
173
174 let old_head = *self.stack_bucket_head_at(bucket_index);
175
176 *self.storage.get_object_mut(new as usize).unwrap() = old_head;
178 set_compressed_stack_trace(
179 &mut self.storage,
180 new as usize + size_of::<Offset>(),
181 compressed_stack_trace,
182 )
183 .unwrap();
184
185 *self.stack_bucket_head_at(bucket_index) = new;
187
188 Ok(new)
189 }
190
191 pub fn intern_compressed_stack_trace(
196 &mut self,
197 compressed_stack_trace: &[u8],
198 ) -> Result<(ResourceKey, bool), crate::Error> {
199 if compressed_stack_trace.len() > StackTraceLength::MAX as usize {
201 return Err(crate::Error::BufferTooBig);
202 }
203
204 let bucket_index = Self::compute_bucket_index(compressed_stack_trace);
206 let (offset, inserted) = match self.find_in_bucket(bucket_index, compressed_stack_trace) {
207 Some(offset) => (offset, false),
208 None => (self.insert_in_bucket(bucket_index, compressed_stack_trace)?, true),
209 };
210
211 let resource_key = ResourceKey(offset + size_of::<Offset>() as Offset);
215 Ok((resource_key, inserted))
216 }
217
218 pub fn insert_thread_info(
220 &mut self,
221 koid: zx::sys::zx_koid_t,
222 name: &zx::Name,
223 ) -> Result<ResourceKey, crate::Error> {
224 let offset = self.allocate(Layout::new::<ThreadInfo>())?;
225 *self.storage.get_object_mut(offset as usize).unwrap() = ThreadInfo { koid, name: *name };
226 Ok(ResourceKey(offset))
227 }
228}
229
230pub struct ResourcesTableReader {
232 storage: ReadOnlyMemoryMappedVmo,
233}
234
235impl ResourcesTableReader {
236 pub unsafe fn new(vmo: &zx::Vmo) -> Result<ResourcesTableReader, crate::Error> {
241 let storage = unsafe { ReadOnlyMemoryMappedVmo::new(vmo)? };
242 Ok(ResourcesTableReader { storage })
243 }
244
245 pub fn get_compressed_stack_trace(
247 &self,
248 resource_key: ResourceKey,
249 ) -> Result<&[u8], crate::Error> {
250 let ResourceKey(offset) = resource_key;
251 get_compressed_stack_trace(&self.storage, offset as usize)
252 }
253
254 pub fn get_thread_info(&self, resource_key: ResourceKey) -> Result<&ThreadInfo, crate::Error> {
256 let ResourceKey(offset) = resource_key;
257 Ok(self.storage.get_object(offset as usize)?)
258 }
259}
260
261fn get_compressed_stack_trace(
262 storage: &ReadOnlyMemoryMappedVmo,
263 byte_offset: usize,
264) -> Result<&[u8], crate::Error> {
265 let header: StackTraceLength = *storage.get_object(byte_offset)?;
267
268 Ok(storage.get_slice(byte_offset + size_of_val(&header), header as usize)?)
270}
271
272fn set_compressed_stack_trace(
273 storage: &mut ReadWriteMemoryMappedVmo,
274 byte_offset: usize,
275 compressed_stack_trace: &[u8],
276) -> Result<(), crate::Error> {
277 let header: StackTraceLength =
278 compressed_stack_trace.len().try_into().map_err(|_| crate::Error::BufferTooBig)?;
279
280 *storage.get_object_mut(byte_offset)? = header;
282
283 storage
285 .get_slice_mut(byte_offset + size_of_val(&header), compressed_stack_trace.len())?
286 .copy_from_slice(compressed_stack_trace);
287
288 Ok(())
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use assert_matches::assert_matches;
295
296 const VMO_SIZE: usize = 4 * 1024 * 1024; struct TestStorage {
300 vmo: zx::Vmo,
301 }
302
303 impl TestStorage {
304 pub fn new(vmo_size: usize) -> (TestStorage, ResourcesTableWriter) {
305 let vmo = zx::Vmo::create(vmo_size as u64).unwrap();
306
307 let writer = unsafe { ResourcesTableWriter::new(&vmo) }.unwrap();
309
310 (TestStorage { vmo }, writer)
311 }
312
313 fn create_reader(&self) -> ResourcesTableReader {
314 unsafe { ResourcesTableReader::new(&self.vmo) }.unwrap()
316 }
317 }
318
319 #[test]
320 fn test_stack_trace_deduplication() {
321 let (storage, mut writer) = TestStorage::new(VMO_SIZE);
322
323 const COUNT: usize = 2 * NUM_STACK_BUCKETS + 1;
327 let mut pairs = Vec::new();
328 for i in 0..COUNT {
329 let stack_trace = i.to_ne_bytes();
331
332 let (resource_key, inserted) =
333 writer.intern_compressed_stack_trace(&stack_trace).unwrap();
334 assert!(inserted, "expected true because the stack trace was not present");
335
336 pairs.push((stack_trace, resource_key));
337 }
338
339 for (stack_trace, expected_resource_key) in &pairs {
341 let (actual_resource_key, inserted) =
342 writer.intern_compressed_stack_trace(stack_trace).unwrap();
343 assert!(!inserted, "expected false because the stack trace is already present");
344 assert_eq!(actual_resource_key, *expected_resource_key);
345 }
346
347 let reader = storage.create_reader();
349 for (expected_stack_trace, resource_key) in &pairs {
350 let actual_stack_trace = reader.get_compressed_stack_trace(*resource_key).unwrap();
351 assert_eq!(actual_stack_trace, *expected_stack_trace);
352 }
353 }
354
355 #[test]
356 fn test_empty_stack_trace() {
357 let (storage, mut writer) = TestStorage::new(VMO_SIZE);
358
359 let (resource_key, inserted) = writer.intern_compressed_stack_trace(&[]).unwrap();
361 assert!(inserted);
362
363 let reader = storage.create_reader();
365 let read_result = reader.get_compressed_stack_trace(resource_key).unwrap();
366 assert_eq!(read_result, []);
367 }
368
369 #[test]
370 fn test_long_stack_traces() {
371 let (storage, mut writer) = TestStorage::new(VMO_SIZE);
372
373 let stack_trace_too_long = vec![0xAA; u16::MAX as usize + 1];
376 let result = writer.intern_compressed_stack_trace(&stack_trace_too_long);
377 assert_matches!(result, Err(crate::Error::BufferTooBig));
378
379 let stack_trace_max_len = vec![0x55; u16::MAX as usize];
381 let (resource_key, _) = writer.intern_compressed_stack_trace(&stack_trace_max_len).unwrap();
382
383 let reader = storage.create_reader();
385 let read_result = reader.get_compressed_stack_trace(resource_key).unwrap();
386 assert_eq!(read_result, stack_trace_max_len);
387 }
388
389 #[test]
390 fn test_write_until_out_of_space() {
391 let (_storage, mut writer) = TestStorage::new(VMO_SIZE);
392
393 for i in 0..=VMO_SIZE {
398 let stack_trace = i.to_ne_bytes();
400
401 if let Err(crate::Error::OutOfSpace) =
402 writer.intern_compressed_stack_trace(&stack_trace)
403 {
404 return; }
406 }
407
408 unreachable!("Inserted more than {} distinct stack traces", VMO_SIZE);
409 }
410
411 #[test]
412 fn test_thread_info() {
413 let (storage, mut writer) = TestStorage::new(VMO_SIZE);
414
415 const FAKE_KOID: zx::sys::zx_koid_t = 1234;
418 const FAKE_NAME: zx::Name = zx::Name::new_lossy("fake-name");
419 let resource_key = writer.insert_thread_info(FAKE_KOID, &FAKE_NAME).unwrap();
420
421 let reader = storage.create_reader();
423 let thread_info = reader.get_thread_info(resource_key).unwrap();
424 assert_eq!(thread_info.koid, FAKE_KOID);
425 assert_eq!(thread_info.name, FAKE_NAME);
426 }
427}