• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/tracing/core/trace_buffer.h"
18 
19 #include <limits>
20 
21 #include "perfetto/base/logging.h"
22 #include "perfetto/ext/base/utils.h"
23 #include "perfetto/ext/tracing/core/shared_memory_abi.h"
24 #include "perfetto/ext/tracing/core/trace_packet.h"
25 #include "perfetto/protozero/proto_utils.h"
26 
27 #define TRACE_BUFFER_VERBOSE_LOGGING() 0  // Set to 1 when debugging unittests.
28 #if TRACE_BUFFER_VERBOSE_LOGGING()
29 #define TRACE_BUFFER_DLOG PERFETTO_DLOG
30 namespace {
31 constexpr char kHexDigits[] = "0123456789abcdef";
HexDump(const uint8_t * src,size_t size)32 std::string HexDump(const uint8_t* src, size_t size) {
33   std::string buf;
34   buf.reserve(4096 * 4);
35   char line[64];
36   char* c = line;
37   for (size_t i = 0; i < size; i++) {
38     *c++ = kHexDigits[(src[i] >> 4) & 0x0f];
39     *c++ = kHexDigits[(src[i] >> 0) & 0x0f];
40     if (i % 16 == 15) {
41       buf.append("\n");
42       buf.append(line);
43       c = line;
44     }
45   }
46   return buf;
47 }
48 }  // namespace
49 #else
50 #define TRACE_BUFFER_DLOG(...) void()
51 #endif
52 
53 namespace perfetto {
54 
55 namespace {
56 constexpr uint8_t kFirstPacketContinuesFromPrevChunk =
57     SharedMemoryABI::ChunkHeader::kFirstPacketContinuesFromPrevChunk;
58 constexpr uint8_t kLastPacketContinuesOnNextChunk =
59     SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk;
60 constexpr uint8_t kChunkNeedsPatching =
61     SharedMemoryABI::ChunkHeader::kChunkNeedsPatching;
62 }  // namespace.
63 
64 constexpr size_t TraceBuffer::ChunkRecord::kMaxSize;
65 constexpr size_t TraceBuffer::InlineChunkHeaderSize = sizeof(ChunkRecord);
66 
67 // static
Create(size_t size_in_bytes,OverwritePolicy pol)68 std::unique_ptr<TraceBuffer> TraceBuffer::Create(size_t size_in_bytes,
69                                                  OverwritePolicy pol) {
70   std::unique_ptr<TraceBuffer> trace_buffer(new TraceBuffer(pol));
71   if (!trace_buffer->Initialize(size_in_bytes))
72     return nullptr;
73   return trace_buffer;
74 }
75 
TraceBuffer(OverwritePolicy pol)76 TraceBuffer::TraceBuffer(OverwritePolicy pol) : overwrite_policy_(pol) {
77   // See comments in ChunkRecord for the rationale of this.
78   static_assert(sizeof(ChunkRecord) == sizeof(SharedMemoryABI::PageHeader) +
79                                            sizeof(SharedMemoryABI::ChunkHeader),
80                 "ChunkRecord out of sync with the layout of SharedMemoryABI");
81 }
82 
83 TraceBuffer::~TraceBuffer() = default;
84 
Initialize(size_t size)85 bool TraceBuffer::Initialize(size_t size) {
86   static_assert(
87       base::kPageSize % sizeof(ChunkRecord) == 0,
88       "sizeof(ChunkRecord) must be an integer divider of a page size");
89   PERFETTO_CHECK(size % base::kPageSize == 0);
90   data_ = base::PagedMemory::Allocate(
91       size, base::PagedMemory::kMayFail | base::PagedMemory::kDontCommit);
92   if (!data_.IsValid()) {
93     PERFETTO_ELOG("Trace buffer allocation failed (size: %zu)", size);
94     return false;
95   }
96   size_ = size;
97   stats_.set_buffer_size(size);
98   max_chunk_size_ = std::min(size, ChunkRecord::kMaxSize);
99   wptr_ = begin();
100   index_.clear();
101   last_chunk_id_written_.clear();
102   read_iter_ = GetReadIterForSequence(index_.end());
103   return true;
104 }
105 
106 // Note: |src| points to a shmem region that is shared with the producer. Assume
107 // that the producer is malicious and will change the content of |src|
108 // while we execute here. Don't do any processing on it other than memcpy().
CopyChunkUntrusted(ProducerID producer_id_trusted,uid_t producer_uid_trusted,WriterID writer_id,ChunkID chunk_id,uint16_t num_fragments,uint8_t chunk_flags,bool chunk_complete,const uint8_t * src,size_t size)109 void TraceBuffer::CopyChunkUntrusted(ProducerID producer_id_trusted,
110                                      uid_t producer_uid_trusted,
111                                      WriterID writer_id,
112                                      ChunkID chunk_id,
113                                      uint16_t num_fragments,
114                                      uint8_t chunk_flags,
115                                      bool chunk_complete,
116                                      const uint8_t* src,
117                                      size_t size) {
118   // |record_size| = |size| + sizeof(ChunkRecord), rounded up to avoid to end
119   // up in a fragmented state where size_to_end() < sizeof(ChunkRecord).
120   const size_t record_size =
121       base::AlignUp<sizeof(ChunkRecord)>(size + sizeof(ChunkRecord));
122   if (PERFETTO_UNLIKELY(record_size > max_chunk_size_)) {
123     stats_.set_abi_violations(stats_.abi_violations() + 1);
124     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
125     return;
126   }
127 
128   TRACE_BUFFER_DLOG("CopyChunk @ %lu, size=%zu", wptr_ - begin(), record_size);
129 
130 #if PERFETTO_DCHECK_IS_ON()
131   changed_since_last_read_ = true;
132 #endif
133 
134   // If the chunk hasn't been completed, we should only consider the first
135   // |num_fragments - 1| packets complete. For simplicity, we simply disregard
136   // the last one when we copy the chunk.
137   if (PERFETTO_UNLIKELY(!chunk_complete)) {
138     if (num_fragments > 0) {
139       num_fragments--;
140       // These flags should only affect the last packet in the chunk. We clear
141       // them, so that TraceBuffer is able to look at the remaining packets in
142       // this chunk.
143       chunk_flags &= ~kLastPacketContinuesOnNextChunk;
144       chunk_flags &= ~kChunkNeedsPatching;
145     }
146   }
147 
148   ChunkRecord record(record_size);
149   record.producer_id = producer_id_trusted;
150   record.chunk_id = chunk_id;
151   record.writer_id = writer_id;
152   record.num_fragments = num_fragments;
153   record.flags = chunk_flags;
154   ChunkMeta::Key key(record);
155 
156   // Check whether we have already copied the same chunk previously. This may
157   // happen if the service scrapes chunks in a potentially incomplete state
158   // before receiving commit requests for them from the producer. Note that the
159   // service may scrape and thus override chunks in arbitrary order since the
160   // chunks aren't ordered in the SMB.
161   const auto it = index_.find(key);
162   if (PERFETTO_UNLIKELY(it != index_.end())) {
163     ChunkMeta* record_meta = &it->second;
164     ChunkRecord* prev = record_meta->chunk_record;
165 
166     // Verify that the old chunk's metadata corresponds to the new one.
167     // Overridden chunks should never change size, since the page layout is
168     // fixed per writer. The number of fragments should also never decrease and
169     // flags should not be removed.
170     if (PERFETTO_UNLIKELY(ChunkMeta::Key(*prev) != key ||
171                           prev->size != record_size ||
172                           prev->num_fragments > num_fragments ||
173                           (prev->flags & chunk_flags) != prev->flags)) {
174       stats_.set_abi_violations(stats_.abi_violations() + 1);
175       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
176       return;
177     }
178 
179     // If we've already started reading from chunk N+1 following this chunk N,
180     // don't override chunk N. Otherwise we may end up reading a packet from
181     // chunk N after having read from chunk N+1, thereby violating sequential
182     // read of packets. This shouldn't happen if the producer is well-behaved,
183     // because it shouldn't start chunk N+1 before completing chunk N.
184     ChunkMeta::Key subsequent_key = key;
185     static_assert(std::numeric_limits<ChunkID>::max() == kMaxChunkID,
186                   "ChunkID wraps");
187     subsequent_key.chunk_id++;
188     const auto subsequent_it = index_.find(subsequent_key);
189     if (subsequent_it != index_.end() &&
190         subsequent_it->second.num_fragments_read > 0) {
191       stats_.set_abi_violations(stats_.abi_violations() + 1);
192       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
193       return;
194     }
195 
196     // If this chunk was previously copied with the same number of fragments and
197     // the number didn't change, there's no need to copy it again. If the
198     // previous chunk was complete already, this should always be the case.
199     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_ ||
200                     !record_meta->is_complete() ||
201                     (chunk_complete && prev->num_fragments == num_fragments));
202     if (prev->num_fragments == num_fragments) {
203       TRACE_BUFFER_DLOG("  skipping recommit of identical chunk");
204       return;
205     }
206 
207     // We should not have read past the last packet.
208     if (record_meta->num_fragments_read > prev->num_fragments) {
209       PERFETTO_ELOG(
210           "TraceBuffer read too many fragments from an incomplete chunk");
211       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
212       return;
213     }
214 
215     uint8_t* wptr = reinterpret_cast<uint8_t*>(prev);
216     TRACE_BUFFER_DLOG("  overriding chunk @ %lu, size=%zu", wptr - begin(),
217                       record_size);
218 
219     // Update chunk meta data stored in the index, as it may have changed.
220     record_meta->num_fragments = num_fragments;
221     record_meta->flags = chunk_flags;
222     record_meta->set_complete(chunk_complete);
223 
224     // Override the ChunkRecord contents at the original |wptr|.
225     TRACE_BUFFER_DLOG("  copying @ [%lu - %lu] %zu", wptr - begin(),
226                       uintptr_t(wptr - begin()) + record_size, record_size);
227     WriteChunkRecord(wptr, record, src, size);
228     TRACE_BUFFER_DLOG("Chunk raw: %s", HexDump(wptr, record_size).c_str());
229     stats_.set_chunks_rewritten(stats_.chunks_rewritten() + 1);
230     return;
231   }
232 
233   if (PERFETTO_UNLIKELY(discard_writes_))
234     return DiscardWrite();
235 
236   // If there isn't enough room from the given write position. Write a padding
237   // record to clear the end of the buffer and wrap back.
238   const size_t cached_size_to_end = size_to_end();
239   if (PERFETTO_UNLIKELY(record_size > cached_size_to_end)) {
240     ssize_t res = DeleteNextChunksFor(cached_size_to_end);
241     if (res == -1)
242       return DiscardWrite();
243     PERFETTO_DCHECK(static_cast<size_t>(res) <= cached_size_to_end);
244     AddPaddingRecord(cached_size_to_end);
245     wptr_ = begin();
246     stats_.set_write_wrap_count(stats_.write_wrap_count() + 1);
247     PERFETTO_DCHECK(size_to_end() >= record_size);
248   }
249 
250   // At this point either |wptr_| points to an untouched part of the buffer
251   // (i.e. *wptr_ == 0) or we are about to overwrite one or more ChunkRecord(s).
252   // In the latter case we need to first figure out where the next valid
253   // ChunkRecord is (if it exists) and add padding between the new record.
254   // Example ((w) == write cursor):
255   //
256   // Initial state (wtpr_ == 0):
257   // |0 (w)    |10               |30                  |50
258   // +---------+-----------------+--------------------+--------------------+
259   // | Chunk 1 | Chunk 2         | Chunk 3            | Chunk 4            |
260   // +---------+-----------------+--------------------+--------------------+
261   //
262   // Let's assume we now want now write a 5th Chunk of size == 35. The final
263   // state should look like this:
264   // |0                                |35 (w)         |50
265   // +---------------------------------+---------------+--------------------+
266   // | Chunk 5                         | Padding Chunk | Chunk 4            |
267   // +---------------------------------+---------------+--------------------+
268 
269   // Deletes all chunks from |wptr_| to |wptr_| + |record_size|.
270   ssize_t del_res = DeleteNextChunksFor(record_size);
271   if (del_res == -1)
272     return DiscardWrite();
273   size_t padding_size = static_cast<size_t>(del_res);
274 
275   // Now first insert the new chunk. At the end, if necessary, add the padding.
276   stats_.set_chunks_written(stats_.chunks_written() + 1);
277   stats_.set_bytes_written(stats_.bytes_written() + record_size);
278   auto it_and_inserted = index_.emplace(
279       key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments, chunk_complete,
280                      chunk_flags, producer_uid_trusted));
281   PERFETTO_DCHECK(it_and_inserted.second);
282   TRACE_BUFFER_DLOG("  copying @ [%lu - %lu] %zu", wptr_ - begin(),
283                     uintptr_t(wptr_ - begin()) + record_size, record_size);
284   WriteChunkRecord(wptr_, record, src, size);
285   TRACE_BUFFER_DLOG("Chunk raw: %s", HexDump(wptr_, record_size).c_str());
286   wptr_ += record_size;
287   if (wptr_ >= end()) {
288     PERFETTO_DCHECK(padding_size == 0);
289     wptr_ = begin();
290     stats_.set_write_wrap_count(stats_.write_wrap_count() + 1);
291   }
292   DcheckIsAlignedAndWithinBounds(wptr_);
293 
294   // Chunks may be received out of order, so only update last_chunk_id if the
295   // new chunk_id is larger. But take into account overflows by only selecting
296   // the new ID if its distance to the latest ID is smaller than half the number
297   // space.
298   //
299   // This accounts for both the case where the new ID has just overflown and
300   // last_chunk_id be updated even though it's smaller (e.g. |chunk_id| = 1 and
301   // |last_chunk_id| = kMaxChunkId; chunk_id - last_chunk_id = 0) and the case
302   // where the new ID is an out-of-order ID right after an overflow and
303   // last_chunk_id shouldn't be updated even though it's larger (e.g. |chunk_id|
304   // = kMaxChunkId and |last_chunk_id| = 1; chunk_id - last_chunk_id =
305   // kMaxChunkId - 1).
306   auto producer_and_writer_id = std::make_pair(producer_id_trusted, writer_id);
307   ChunkID& last_chunk_id = last_chunk_id_written_[producer_and_writer_id];
308   static_assert(std::numeric_limits<ChunkID>::max() == kMaxChunkID,
309                 "This code assumes that ChunkID wraps at kMaxChunkID");
310   if (chunk_id - last_chunk_id < kMaxChunkID / 2) {
311     last_chunk_id = chunk_id;
312   } else {
313     stats_.set_chunks_committed_out_of_order(
314         stats_.chunks_committed_out_of_order() + 1);
315   }
316 
317   if (padding_size)
318     AddPaddingRecord(padding_size);
319 }
320 
DeleteNextChunksFor(size_t bytes_to_clear)321 ssize_t TraceBuffer::DeleteNextChunksFor(size_t bytes_to_clear) {
322   PERFETTO_CHECK(!discard_writes_);
323 
324   // Find the position of the first chunk which begins at or after
325   // (|wptr_| + |bytes|). Note that such a chunk might not exist and we might
326   // either reach the end of the buffer or a zeroed region of the buffer.
327   uint8_t* next_chunk_ptr = wptr_;
328   uint8_t* search_end = wptr_ + bytes_to_clear;
329   TRACE_BUFFER_DLOG("Delete [%zu %zu]", wptr_ - begin(), search_end - begin());
330   DcheckIsAlignedAndWithinBounds(wptr_);
331   PERFETTO_DCHECK(search_end <= end());
332   std::vector<ChunkMap::iterator> index_delete;
333   uint64_t chunks_overwritten = stats_.chunks_overwritten();
334   uint64_t bytes_overwritten = stats_.bytes_overwritten();
335   uint64_t padding_bytes_cleared = stats_.padding_bytes_cleared();
336   while (next_chunk_ptr < search_end) {
337     const ChunkRecord& next_chunk = *GetChunkRecordAt(next_chunk_ptr);
338     TRACE_BUFFER_DLOG(
339         "  scanning chunk [%zu %zu] (valid=%d)", next_chunk_ptr - begin(),
340         next_chunk_ptr - begin() + next_chunk.size, next_chunk.is_valid());
341 
342     // We just reached the untouched part of the buffer, it's going to be all
343     // zeroes from here to end().
344     // Optimization: if during Initialize() we fill the buffer with padding
345     // records we could get rid of this branch.
346     if (PERFETTO_UNLIKELY(!next_chunk.is_valid())) {
347       // This should happen only at the first iteration. The zeroed area can
348       // only begin precisely at the |wptr_|, not after. Otherwise it means that
349       // we wrapped but screwed up the ChunkRecord chain.
350       PERFETTO_DCHECK(next_chunk_ptr == wptr_);
351       return 0;
352     }
353 
354     // Remove |next_chunk| from the index, unless it's a padding record (padding
355     // records are not part of the index).
356     if (PERFETTO_LIKELY(!next_chunk.is_padding)) {
357       ChunkMeta::Key key(next_chunk);
358       auto it = index_.find(key);
359       bool will_remove = false;
360       if (PERFETTO_LIKELY(it != index_.end())) {
361         const ChunkMeta& meta = it->second;
362         if (PERFETTO_UNLIKELY(meta.num_fragments_read < meta.num_fragments)) {
363           if (overwrite_policy_ == kDiscard)
364             return -1;
365           chunks_overwritten++;
366           bytes_overwritten += next_chunk.size;
367         }
368         index_delete.push_back(it);
369         will_remove = true;
370       }
371       TRACE_BUFFER_DLOG(
372           "  del index {%" PRIu32 ",%" PRIu32 ",%u} @ [%lu - %lu] %d",
373           key.producer_id, key.writer_id, key.chunk_id,
374           next_chunk_ptr - begin(), next_chunk_ptr - begin() + next_chunk.size,
375           will_remove);
376       PERFETTO_DCHECK(will_remove);
377     } else {
378       padding_bytes_cleared += next_chunk.size;
379     }
380 
381     next_chunk_ptr += next_chunk.size;
382 
383     // We should never hit this, unless we managed to screw up while writing
384     // to the buffer and breaking the ChunkRecord(s) chain.
385     // TODO(primiano): Write more meaningful logging with the status of the
386     // buffer, to get more actionable bugs in case we hit this.
387     PERFETTO_CHECK(next_chunk_ptr <= end());
388   }
389 
390   // Remove from the index.
391   for (auto it : index_delete) {
392     index_.erase(it);
393   }
394   stats_.set_chunks_overwritten(chunks_overwritten);
395   stats_.set_bytes_overwritten(bytes_overwritten);
396   stats_.set_padding_bytes_cleared(padding_bytes_cleared);
397 
398   PERFETTO_DCHECK(next_chunk_ptr >= search_end && next_chunk_ptr <= end());
399   return static_cast<ssize_t>(next_chunk_ptr - search_end);
400 }
401 
AddPaddingRecord(size_t size)402 void TraceBuffer::AddPaddingRecord(size_t size) {
403   PERFETTO_DCHECK(size >= sizeof(ChunkRecord) && size <= ChunkRecord::kMaxSize);
404   ChunkRecord record(size);
405   record.is_padding = 1;
406   TRACE_BUFFER_DLOG("AddPaddingRecord @ [%lu - %lu] %zu", wptr_ - begin(),
407                     uintptr_t(wptr_ - begin()) + size, size);
408   WriteChunkRecord(wptr_, record, nullptr, size - sizeof(ChunkRecord));
409   stats_.set_padding_bytes_written(stats_.padding_bytes_written() + size);
410   // |wptr_| is deliberately not advanced when writing a padding record.
411 }
412 
TryPatchChunkContents(ProducerID producer_id,WriterID writer_id,ChunkID chunk_id,const Patch * patches,size_t patches_size,bool other_patches_pending)413 bool TraceBuffer::TryPatchChunkContents(ProducerID producer_id,
414                                         WriterID writer_id,
415                                         ChunkID chunk_id,
416                                         const Patch* patches,
417                                         size_t patches_size,
418                                         bool other_patches_pending) {
419   ChunkMeta::Key key(producer_id, writer_id, chunk_id);
420   auto it = index_.find(key);
421   if (it == index_.end()) {
422     stats_.set_patches_failed(stats_.patches_failed() + 1);
423     return false;
424   }
425   ChunkMeta& chunk_meta = it->second;
426 
427   // Check that the index is consistent with the actual ProducerID/WriterID
428   // stored in the ChunkRecord.
429   PERFETTO_DCHECK(ChunkMeta::Key(*chunk_meta.chunk_record) == key);
430   uint8_t* chunk_begin = reinterpret_cast<uint8_t*>(chunk_meta.chunk_record);
431   PERFETTO_DCHECK(chunk_begin >= begin());
432   uint8_t* chunk_end = chunk_begin + chunk_meta.chunk_record->size;
433   PERFETTO_DCHECK(chunk_end <= end());
434 
435   static_assert(Patch::kSize == SharedMemoryABI::kPacketHeaderSize,
436                 "Patch::kSize out of sync with SharedMemoryABI");
437 
438   for (size_t i = 0; i < patches_size; i++) {
439     uint8_t* ptr =
440         chunk_begin + sizeof(ChunkRecord) + patches[i].offset_untrusted;
441     TRACE_BUFFER_DLOG("PatchChunk {%" PRIu32 ",%" PRIu32
442                       ",%u} size=%zu @ %zu with {%02x %02x %02x %02x} cur "
443                       "{%02x %02x %02x %02x}",
444                       producer_id, writer_id, chunk_id, chunk_end - chunk_begin,
445                       patches[i].offset_untrusted, patches[i].data[0],
446                       patches[i].data[1], patches[i].data[2],
447                       patches[i].data[3], ptr[0], ptr[1], ptr[2], ptr[3]);
448     if (ptr < chunk_begin + sizeof(ChunkRecord) ||
449         ptr > chunk_end - Patch::kSize) {
450       // Either the IPC was so slow and in the meantime the writer managed to
451       // wrap over |chunk_id| or the producer sent a malicious IPC.
452       stats_.set_patches_failed(stats_.patches_failed() + 1);
453       return false;
454     }
455 
456     // DCHECK that we are writing into a zero-filled size field and not into
457     // valid data. It relies on ScatteredStreamWriter::ReserveBytes() to
458     // zero-fill reservations in debug builds.
459     char zero[Patch::kSize]{};
460     PERFETTO_DCHECK(memcmp(ptr, &zero, Patch::kSize) == 0);
461 
462     memcpy(ptr, &patches[i].data[0], Patch::kSize);
463   }
464   TRACE_BUFFER_DLOG(
465       "Chunk raw (after patch): %s",
466       HexDump(chunk_begin, chunk_meta.chunk_record->size).c_str());
467 
468   stats_.set_patches_succeeded(stats_.patches_succeeded() + patches_size);
469   if (!other_patches_pending) {
470     chunk_meta.flags &= ~kChunkNeedsPatching;
471     chunk_meta.chunk_record->flags = chunk_meta.flags;
472   }
473   return true;
474 }
475 
BeginRead()476 void TraceBuffer::BeginRead() {
477   read_iter_ = GetReadIterForSequence(index_.begin());
478 #if PERFETTO_DCHECK_IS_ON()
479   changed_since_last_read_ = false;
480 #endif
481 }
482 
GetReadIterForSequence(ChunkMap::iterator seq_begin)483 TraceBuffer::SequenceIterator TraceBuffer::GetReadIterForSequence(
484     ChunkMap::iterator seq_begin) {
485   SequenceIterator iter;
486   iter.seq_begin = seq_begin;
487   if (seq_begin == index_.end()) {
488     iter.cur = iter.seq_end = index_.end();
489     return iter;
490   }
491 
492 #if PERFETTO_DCHECK_IS_ON()
493   // Either |seq_begin| is == index_.begin() or the item immediately before must
494   // belong to a different {ProducerID, WriterID} sequence.
495   if (seq_begin != index_.begin() && seq_begin != index_.end()) {
496     auto prev_it = seq_begin;
497     prev_it--;
498     PERFETTO_DCHECK(
499         seq_begin == index_.begin() ||
500         std::tie(prev_it->first.producer_id, prev_it->first.writer_id) <
501             std::tie(seq_begin->first.producer_id, seq_begin->first.writer_id));
502   }
503 #endif
504 
505   // Find the first entry that has a greater {ProducerID, WriterID} (or just
506   // index_.end() if we reached the end).
507   ChunkMeta::Key key = seq_begin->first;  // Deliberate copy.
508   key.chunk_id = kMaxChunkID;
509   iter.seq_end = index_.upper_bound(key);
510   PERFETTO_DCHECK(iter.seq_begin != iter.seq_end);
511 
512   // Now find the first entry between [seq_begin, seq_end) that is
513   // > last_chunk_id_written_. This is where we the sequence will start (see
514   // notes about wrapping of IDs in the header).
515   auto producer_and_writer_id = std::make_pair(key.producer_id, key.writer_id);
516   PERFETTO_DCHECK(last_chunk_id_written_.count(producer_and_writer_id));
517   iter.wrapping_id = last_chunk_id_written_[producer_and_writer_id];
518   key.chunk_id = iter.wrapping_id;
519   iter.cur = index_.upper_bound(key);
520   if (iter.cur == iter.seq_end)
521     iter.cur = iter.seq_begin;
522   return iter;
523 }
524 
MoveNext()525 void TraceBuffer::SequenceIterator::MoveNext() {
526   // Stop iterating when we reach the end of the sequence.
527   // Note: |seq_begin| might be == |seq_end|.
528   if (cur == seq_end || cur->first.chunk_id == wrapping_id) {
529     cur = seq_end;
530     return;
531   }
532 
533   // If the current chunk wasn't completed yet, we shouldn't advance past it as
534   // it may be rewritten with additional packets.
535   if (!cur->second.is_complete()) {
536     cur = seq_end;
537     return;
538   }
539 
540   ChunkID last_chunk_id = cur->first.chunk_id;
541   if (++cur == seq_end)
542     cur = seq_begin;
543 
544   // There may be a missing chunk in the sequence of chunks, in which case the
545   // next chunk's ID won't follow the last one's. If so, skip the rest of the
546   // sequence. We'll return to it later once the hole is filled.
547   if (last_chunk_id + 1 != cur->first.chunk_id)
548     cur = seq_end;
549 }
550 
ReadNextTracePacket(TracePacket * packet,PacketSequenceProperties * sequence_properties,bool * previous_packet_on_sequence_dropped)551 bool TraceBuffer::ReadNextTracePacket(
552     TracePacket* packet,
553     PacketSequenceProperties* sequence_properties,
554     bool* previous_packet_on_sequence_dropped) {
555   // Note: MoveNext() moves only within the next chunk within the same
556   // {ProducerID, WriterID} sequence. Here we want to:
557   // - return the next patched+complete packet in the current sequence, if any.
558   // - return the first patched+complete packet in the next sequence, if any.
559   // - return false if none of the above is found.
560   TRACE_BUFFER_DLOG("ReadNextTracePacket()");
561 
562   // Just in case we forget to initialize these below.
563   *sequence_properties = {0, kInvalidUid, 0};
564   *previous_packet_on_sequence_dropped = false;
565 
566   // At the start of each sequence iteration, we consider the last read packet
567   // dropped. While iterating over the chunks in the sequence, we update this
568   // flag based on our knowledge about the last packet that was read from each
569   // chunk (|last_read_packet_skipped| in ChunkMeta).
570   bool previous_packet_dropped = true;
571 
572 #if PERFETTO_DCHECK_IS_ON()
573   PERFETTO_DCHECK(!changed_since_last_read_);
574 #endif
575   for (;; read_iter_.MoveNext()) {
576     if (PERFETTO_UNLIKELY(!read_iter_.is_valid())) {
577       // We ran out of chunks in the current {ProducerID, WriterID} sequence or
578       // we just reached the index_.end().
579 
580       if (PERFETTO_UNLIKELY(read_iter_.seq_end == index_.end()))
581         return false;
582 
583       // We reached the end of sequence, move to the next one.
584       // Note: ++read_iter_.seq_end might become index_.end(), but
585       // GetReadIterForSequence() knows how to deal with that.
586       read_iter_ = GetReadIterForSequence(read_iter_.seq_end);
587       PERFETTO_DCHECK(read_iter_.is_valid() && read_iter_.cur != index_.end());
588       previous_packet_dropped = true;
589     }
590 
591     ChunkMeta* chunk_meta = &*read_iter_;
592 
593     // If the chunk has holes that are awaiting to be patched out-of-band,
594     // skip the current sequence and move to the next one.
595     if (chunk_meta->flags & kChunkNeedsPatching) {
596       read_iter_.MoveToEnd();
597       continue;
598     }
599 
600     const ProducerID trusted_producer_id = read_iter_.producer_id();
601     const WriterID writer_id = read_iter_.writer_id();
602     const uid_t trusted_uid = chunk_meta->trusted_uid;
603 
604     // At this point we have a chunk in |chunk_meta| that has not been fully
605     // read. We don't know yet whether we have enough data to read the full
606     // packet (in the case it's fragmented over several chunks) and we are about
607     // to find that out. Specifically:
608     // A) If the first fragment is unread and is a fragment continuing from a
609     //    previous chunk, it means we have missed the previous ChunkID. In
610     //    fact, if this wasn't the case, a previous call to ReadNext() shouldn't
611     //    have moved the cursor to this chunk.
612     // B) Any fragment > 0 && < last is always readable. By definition an inner
613     //    packet is never fragmented and hence doesn't require neither stitching
614     //    nor any out-of-band patching. The same applies to the last packet
615     //    iff it doesn't continue on the next chunk.
616     // C) If the last packet (which might be also the only packet in the chunk)
617     //    is a fragment and continues on the next chunk, we peek at the next
618     //    chunks and, if we have all of them, mark as read and move the cursor.
619     //
620     // +---------------+   +-------------------+  +---------------+
621     // | ChunkID: 1    |   | ChunkID: 2        |  | ChunkID: 3    |
622     // |---------------+   +-------------------+  +---------------+
623     // | Packet 1      |   |                   |  | ... Packet 3  |
624     // | Packet 2      |   | ... Packet 3  ... |  | Packet 4      |
625     // | Packet 3  ... |   |                   |  | Packet 5 ...  |
626     // +---------------+   +-------------------+  +---------------+
627 
628     PERFETTO_DCHECK(chunk_meta->num_fragments_read <=
629                     chunk_meta->num_fragments);
630 
631     // If we didn't read any packets from this chunk, the last packet was from
632     // the previous chunk we iterated over; so don't update
633     // |previous_packet_dropped| in this case.
634     if (chunk_meta->num_fragments_read > 0)
635       previous_packet_dropped = chunk_meta->last_read_packet_skipped();
636 
637     while (chunk_meta->num_fragments_read < chunk_meta->num_fragments) {
638       enum { kSkip = 0, kReadOnePacket, kTryReadAhead } action;
639       if (chunk_meta->num_fragments_read == 0) {
640         if (chunk_meta->flags & kFirstPacketContinuesFromPrevChunk) {
641           action = kSkip;  // Case A.
642         } else if (chunk_meta->num_fragments == 1 &&
643                    (chunk_meta->flags & kLastPacketContinuesOnNextChunk)) {
644           action = kTryReadAhead;  // Case C.
645         } else {
646           action = kReadOnePacket;  // Case B.
647         }
648       } else if (chunk_meta->num_fragments_read <
649                      chunk_meta->num_fragments - 1 ||
650                  !(chunk_meta->flags & kLastPacketContinuesOnNextChunk)) {
651         action = kReadOnePacket;  // Case B.
652       } else {
653         action = kTryReadAhead;  // Case C.
654       }
655 
656       TRACE_BUFFER_DLOG("  chunk %u, packet %hu of %hu, action=%d",
657                         read_iter_.chunk_id(), chunk_meta->num_fragments_read,
658                         chunk_meta->num_fragments, action);
659 
660       if (action == kSkip) {
661         // This fragment will be skipped forever, not just in this ReadPacket()
662         // iteration. This happens by virtue of ReadNextPacketInChunk()
663         // incrementing the |num_fragments_read| and marking the fragment as
664         // read even if we didn't really.
665         ReadNextPacketInChunk(chunk_meta, nullptr);
666         chunk_meta->set_last_read_packet_skipped(true);
667         previous_packet_dropped = true;
668         continue;
669       }
670 
671       if (action == kReadOnePacket) {
672         // The easy peasy case B.
673         ReadPacketResult result = ReadNextPacketInChunk(chunk_meta, packet);
674 
675         if (PERFETTO_LIKELY(result == ReadPacketResult::kSucceeded)) {
676           *sequence_properties = {trusted_producer_id, trusted_uid, writer_id};
677           *previous_packet_on_sequence_dropped = previous_packet_dropped;
678           return true;
679         } else if (result == ReadPacketResult::kFailedEmptyPacket) {
680           // We can ignore and skip empty packets.
681           PERFETTO_DCHECK(packet->slices().empty());
682           continue;
683         }
684 
685         // In extremely rare cases (producer bugged / malicious) the chunk might
686         // contain an invalid fragment. In such case we don't want to stall the
687         // sequence but just skip the chunk and move on. ReadNextPacketInChunk()
688         // marks the chunk as fully read, so we don't attempt to read from it
689         // again in a future call to ReadBuffers(). It also already records an
690         // abi violation for this.
691         PERFETTO_DCHECK(result == ReadPacketResult::kFailedInvalidPacket);
692         chunk_meta->set_last_read_packet_skipped(true);
693         previous_packet_dropped = true;
694         break;
695       }
696 
697       PERFETTO_DCHECK(action == kTryReadAhead);
698       ReadAheadResult ra_res = ReadAhead(packet);
699       if (ra_res == ReadAheadResult::kSucceededReturnSlices) {
700         stats_.set_readaheads_succeeded(stats_.readaheads_succeeded() + 1);
701         *sequence_properties = {trusted_producer_id, trusted_uid, writer_id};
702         *previous_packet_on_sequence_dropped = previous_packet_dropped;
703         return true;
704       }
705 
706       if (ra_res == ReadAheadResult::kFailedMoveToNextSequence) {
707         // readahead didn't find a contiguous packet sequence. We'll try again
708         // on the next ReadPacket() call.
709         stats_.set_readaheads_failed(stats_.readaheads_failed() + 1);
710 
711         // TODO(primiano): optimization: this MoveToEnd() is the reason why
712         // MoveNext() (that is called in the outer for(;;MoveNext)) needs to
713         // deal gracefully with the case of |cur|==|seq_end|. Maybe we can do
714         // something to avoid that check by reshuffling the code here?
715         read_iter_.MoveToEnd();
716 
717         // This break will go back to beginning of the for(;;MoveNext()). That
718         // will move to the next sequence because we set the read iterator to
719         // its end.
720         break;
721       }
722 
723       PERFETTO_DCHECK(ra_res == ReadAheadResult::kFailedStayOnSameSequence);
724 
725       // In this case ReadAhead() might advance |read_iter_|, so we need to
726       // re-cache the |chunk_meta| pointer to point to the current chunk.
727       chunk_meta = &*read_iter_;
728       chunk_meta->set_last_read_packet_skipped(true);
729       previous_packet_dropped = true;
730     }  // while(...)  [iterate over packet fragments for the current chunk].
731   }    // for(;;MoveNext()) [iterate over chunks].
732 }
733 
ReadAhead(TracePacket * packet)734 TraceBuffer::ReadAheadResult TraceBuffer::ReadAhead(TracePacket* packet) {
735   static_assert(static_cast<ChunkID>(kMaxChunkID + 1) == 0,
736                 "relying on kMaxChunkID to wrap naturally");
737   TRACE_BUFFER_DLOG(" readahead start @ chunk %u", read_iter_.chunk_id());
738   ChunkID next_chunk_id = read_iter_.chunk_id() + 1;
739   SequenceIterator it = read_iter_;
740   for (it.MoveNext(); it.is_valid(); it.MoveNext(), next_chunk_id++) {
741     // We should stay within the same sequence while iterating here.
742     PERFETTO_DCHECK(it.producer_id() == read_iter_.producer_id() &&
743                     it.writer_id() == read_iter_.writer_id());
744 
745     TRACE_BUFFER_DLOG("   expected chunk ID: %u, actual ID: %u", next_chunk_id,
746                       it.chunk_id());
747 
748     if (PERFETTO_UNLIKELY((*it).num_fragments == 0))
749       continue;
750 
751     // If we miss the next chunk, stop looking in the current sequence and
752     // try another sequence. This chunk might come in the near future.
753     // The second condition is the edge case of a buggy/malicious
754     // producer. The ChunkID is contiguous but its flags don't make sense.
755     if (it.chunk_id() != next_chunk_id ||
756         PERFETTO_UNLIKELY(
757             !((*it).flags & kFirstPacketContinuesFromPrevChunk))) {
758       return ReadAheadResult::kFailedMoveToNextSequence;
759     }
760 
761     // If the chunk is contiguous but has not been patched yet move to the next
762     // sequence and try coming back here on the next ReadNextTracePacket() call.
763     // TODO(primiano): add a test to cover this, it's a subtle case.
764     if ((*it).flags & kChunkNeedsPatching)
765       return ReadAheadResult::kFailedMoveToNextSequence;
766 
767     // This is the case of an intermediate chunk which contains only one
768     // fragment which continues on the next chunk. This is the case for large
769     // packets, e.g.: [Packet0, Packet1(0)] [Packet1(1)] [Packet1(2), ...]
770     // (Packet1(X) := fragment X of Packet1).
771     if ((*it).num_fragments == 1 &&
772         ((*it).flags & kLastPacketContinuesOnNextChunk)) {
773       continue;
774     }
775 
776     // We made it! We got all fragments for the packet without holes.
777     TRACE_BUFFER_DLOG("  readahead success @ chunk %u", it.chunk_id());
778     PERFETTO_DCHECK(((*it).num_fragments == 1 &&
779                      !((*it).flags & kLastPacketContinuesOnNextChunk)) ||
780                     (*it).num_fragments > 1);
781 
782     // Now let's re-iterate over the [read_iter_, it] sequence and mark
783     // all the fragments as read.
784     bool packet_corruption = false;
785     for (;;) {
786       PERFETTO_DCHECK(read_iter_.is_valid());
787       TRACE_BUFFER_DLOG("    commit chunk %u", read_iter_.chunk_id());
788       if (PERFETTO_LIKELY((*read_iter_).num_fragments > 0)) {
789         // In the unlikely case of a corrupted packet (corrupted or empty
790         // fragment), invalidate the all stitching and move on to the next chunk
791         // in the same sequence, if any.
792         packet_corruption |= ReadNextPacketInChunk(&*read_iter_, packet) ==
793                              ReadPacketResult::kFailedInvalidPacket;
794       }
795       if (read_iter_.cur == it.cur)
796         break;
797       read_iter_.MoveNext();
798     }  // for(;;)
799     PERFETTO_DCHECK(read_iter_.cur == it.cur);
800 
801     if (PERFETTO_UNLIKELY(packet_corruption)) {
802       // ReadNextPacketInChunk() already records an abi violation for this case.
803       *packet = TracePacket();  // clear.
804       return ReadAheadResult::kFailedStayOnSameSequence;
805     }
806 
807     return ReadAheadResult::kSucceededReturnSlices;
808   }  // for(it...)  [readahead loop]
809   return ReadAheadResult::kFailedMoveToNextSequence;
810 }
811 
ReadNextPacketInChunk(ChunkMeta * chunk_meta,TracePacket * packet)812 TraceBuffer::ReadPacketResult TraceBuffer::ReadNextPacketInChunk(
813     ChunkMeta* chunk_meta,
814     TracePacket* packet) {
815   PERFETTO_DCHECK(chunk_meta->num_fragments_read < chunk_meta->num_fragments);
816   PERFETTO_DCHECK(!(chunk_meta->flags & kChunkNeedsPatching));
817 
818   const uint8_t* record_begin =
819       reinterpret_cast<const uint8_t*>(chunk_meta->chunk_record);
820   const uint8_t* record_end = record_begin + chunk_meta->chunk_record->size;
821   const uint8_t* packets_begin = record_begin + sizeof(ChunkRecord);
822   const uint8_t* packet_begin = packets_begin + chunk_meta->cur_fragment_offset;
823 
824   if (PERFETTO_UNLIKELY(packet_begin < packets_begin ||
825                         packet_begin >= record_end)) {
826     // The producer has a bug or is malicious and did declare that the chunk
827     // contains more packets beyond its boundaries.
828     stats_.set_abi_violations(stats_.abi_violations() + 1);
829     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
830     chunk_meta->cur_fragment_offset = 0;
831     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
832     if (PERFETTO_LIKELY(chunk_meta->is_complete())) {
833       stats_.set_chunks_read(stats_.chunks_read() + 1);
834       stats_.set_bytes_read(stats_.bytes_read() +
835                             chunk_meta->chunk_record->size);
836     }
837     return ReadPacketResult::kFailedInvalidPacket;
838   }
839 
840   // A packet (or a fragment) starts with a varint stating its size, followed
841   // by its content. The varint shouldn't be larger than 4 bytes (just in case
842   // the producer is using a redundant encoding)
843   uint64_t packet_size = 0;
844   const uint8_t* header_end =
845       std::min(packet_begin + protozero::proto_utils::kMessageLengthFieldSize,
846                record_end);
847   const uint8_t* packet_data = protozero::proto_utils::ParseVarInt(
848       packet_begin, header_end, &packet_size);
849 
850   const uint8_t* next_packet = packet_data + packet_size;
851   if (PERFETTO_UNLIKELY(next_packet <= packet_begin ||
852                         next_packet > record_end)) {
853     // In BufferExhaustedPolicy::kDrop mode, TraceWriter may abort a fragmented
854     // packet by writing an invalid size in the last fragment's header. We
855     // should handle this case without recording an ABI violation (since Android
856     // R).
857     if (packet_size != SharedMemoryABI::kPacketSizeDropPacket) {
858       stats_.set_abi_violations(stats_.abi_violations() + 1);
859       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
860     } else {
861       stats_.set_trace_writer_packet_loss(stats_.trace_writer_packet_loss() +
862                                           1);
863     }
864     chunk_meta->cur_fragment_offset = 0;
865     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
866     if (PERFETTO_LIKELY(chunk_meta->is_complete())) {
867       stats_.set_chunks_read(stats_.chunks_read() + 1);
868       stats_.set_bytes_read(stats_.bytes_read() +
869                             chunk_meta->chunk_record->size);
870     }
871     return ReadPacketResult::kFailedInvalidPacket;
872   }
873 
874   chunk_meta->cur_fragment_offset =
875       static_cast<uint16_t>(next_packet - packets_begin);
876   chunk_meta->num_fragments_read++;
877 
878   if (PERFETTO_UNLIKELY(chunk_meta->num_fragments_read ==
879                             chunk_meta->num_fragments &&
880                         chunk_meta->is_complete())) {
881     stats_.set_chunks_read(stats_.chunks_read() + 1);
882     stats_.set_bytes_read(stats_.bytes_read() + chunk_meta->chunk_record->size);
883   } else {
884     // We have at least one more packet to parse. It should be within the chunk.
885     if (chunk_meta->cur_fragment_offset + sizeof(ChunkRecord) >=
886         chunk_meta->chunk_record->size) {
887       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
888     }
889   }
890 
891   chunk_meta->set_last_read_packet_skipped(false);
892 
893   if (PERFETTO_UNLIKELY(packet_size == 0))
894     return ReadPacketResult::kFailedEmptyPacket;
895 
896   if (PERFETTO_LIKELY(packet))
897     packet->AddSlice(packet_data, static_cast<size_t>(packet_size));
898 
899   return ReadPacketResult::kSucceeded;
900 }
901 
DiscardWrite()902 void TraceBuffer::DiscardWrite() {
903   PERFETTO_DCHECK(overwrite_policy_ == kDiscard);
904   discard_writes_ = true;
905   stats_.set_chunks_discarded(stats_.chunks_discarded() + 1);
906   TRACE_BUFFER_DLOG("  discarding write");
907 }
908 
909 }  // namespace perfetto
910