Home | History | Annotate | Download | only in core
      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 <sys/mman.h>
     20 #include <limits>
     21 
     22 #include "perfetto/base/logging.h"
     23 #include "perfetto/protozero/proto_utils.h"
     24 #include "perfetto/tracing/core/shared_memory_abi.h"
     25 #include "perfetto/tracing/core/trace_packet.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 std::string HexDump(const uint8_t* src, size_t size) {
     32   std::string buf;
     33   buf.reserve(4096 * 4);
     34   char line[64];
     35   char* c = line;
     36   for (size_t i = 0; i < size; i++) {
     37     c += sprintf(c, "%02x ", src[i]);
     38     if (i % 16 == 15) {
     39       buf.append("\n");
     40       buf.append(line);
     41       c = line;
     42     }
     43   }
     44   return buf;
     45 }
     46 }  // namespace
     47 #else
     48 #define TRACE_BUFFER_DLOG(...) void()
     49 #endif
     50 
     51 namespace perfetto {
     52 
     53 namespace {
     54 constexpr uint8_t kFirstPacketContinuesFromPrevChunk =
     55     SharedMemoryABI::ChunkHeader::kFirstPacketContinuesFromPrevChunk;
     56 constexpr uint8_t kLastPacketContinuesOnNextChunk =
     57     SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk;
     58 constexpr uint8_t kChunkNeedsPatching =
     59     SharedMemoryABI::ChunkHeader::kChunkNeedsPatching;
     60 }  // namespace.
     61 
     62 constexpr size_t TraceBuffer::ChunkRecord::kMaxSize;
     63 constexpr size_t TraceBuffer::InlineChunkHeaderSize = sizeof(ChunkRecord);
     64 
     65 // static
     66 std::unique_ptr<TraceBuffer> TraceBuffer::Create(size_t size_in_bytes) {
     67   std::unique_ptr<TraceBuffer> trace_buffer(new TraceBuffer());
     68   if (!trace_buffer->Initialize(size_in_bytes))
     69     return nullptr;
     70   return trace_buffer;
     71 }
     72 
     73 TraceBuffer::TraceBuffer() {
     74   // See comments in ChunkRecord for the rationale of this.
     75   static_assert(sizeof(ChunkRecord) == sizeof(SharedMemoryABI::PageHeader) +
     76                                            sizeof(SharedMemoryABI::ChunkHeader),
     77                 "ChunkRecord out of sync with the layout of SharedMemoryABI");
     78 }
     79 
     80 TraceBuffer::~TraceBuffer() = default;
     81 
     82 bool TraceBuffer::Initialize(size_t size) {
     83   static_assert(
     84       base::kPageSize % sizeof(ChunkRecord) == 0,
     85       "sizeof(ChunkRecord) must be an integer divider of a page size");
     86   PERFETTO_CHECK(size % base::kPageSize == 0);
     87   data_ = base::PageAllocator::AllocateMayFail(size);
     88   if (!data_) {
     89     PERFETTO_ELOG("Trace buffer allocation failed (size: %zu)", size);
     90     return false;
     91   }
     92   size_ = size;
     93   max_chunk_size_ = std::min(size, ChunkRecord::kMaxSize);
     94   wptr_ = begin();
     95   index_.clear();
     96   last_chunk_id_.clear();
     97   read_iter_ = GetReadIterForSequence(index_.end());
     98   return true;
     99 }
    100 
    101 // Note: |src| points to a shmem region that is shared with the producer. Assume
    102 // that the producer is malicious and will change the content of |src|
    103 // while we execute here. Don't do any processing on it other than memcpy().
    104 void TraceBuffer::CopyChunkUntrusted(ProducerID producer_id_trusted,
    105                                      uid_t producer_uid_trusted,
    106                                      WriterID writer_id,
    107                                      ChunkID chunk_id,
    108                                      uint16_t num_fragments,
    109                                      uint8_t chunk_flags,
    110                                      const uint8_t* src,
    111                                      size_t size) {
    112   // |record_size| = |size| + sizeof(ChunkRecord), rounded up to avoid to end
    113   // up in a fragmented state where size_to_end() < sizeof(ChunkRecord).
    114   const size_t record_size =
    115       base::AlignUp<sizeof(ChunkRecord)>(size + sizeof(ChunkRecord));
    116   if (PERFETTO_UNLIKELY(record_size > max_chunk_size_)) {
    117     stats_.abi_violations++;
    118     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    119     return;
    120   }
    121 
    122   TRACE_BUFFER_DLOG("CopyChunk @ %lu, size=%zu", wptr_ - begin(), record_size);
    123 
    124 #if PERFETTO_DCHECK_IS_ON()
    125   changed_since_last_read_ = true;
    126 #endif
    127 
    128   // If there isn't enough room from the given write position. Write a padding
    129   // record to clear the end of the buffer and wrap back.
    130   const size_t cached_size_to_end = size_to_end();
    131   if (PERFETTO_UNLIKELY(record_size > cached_size_to_end)) {
    132     size_t res = DeleteNextChunksFor(cached_size_to_end);
    133     PERFETTO_DCHECK(res <= cached_size_to_end);
    134     AddPaddingRecord(cached_size_to_end);
    135     wptr_ = begin();
    136     stats_.write_wrap_count++;
    137     PERFETTO_DCHECK(size_to_end() >= record_size);
    138   }
    139 
    140   ChunkRecord record(record_size);
    141   record.producer_id = producer_id_trusted;
    142   record.chunk_id = chunk_id;
    143   record.writer_id = writer_id;
    144   record.num_fragments = num_fragments;
    145   record.flags = chunk_flags;
    146 
    147   // At this point either |wptr_| points to an untouched part of the buffer
    148   // (i.e. *wptr_ == 0) or we are about to overwrite one or more ChunkRecord(s).
    149   // In the latter case we need to first figure out where the next valid
    150   // ChunkRecord is (if it exists) and add padding between the new record.
    151   // Example ((w) == write cursor):
    152   //
    153   // Initial state (wtpr_ == 0):
    154   // |0 (w)    |10               |30                  |50
    155   // +---------+-----------------+--------------------+--------------------+
    156   // | Chunk 1 | Chunk 2         | Chunk 3            | Chunk 4            |
    157   // +---------+-----------------+--------------------+--------------------+
    158   //
    159   // Let's assume we now want now write a 5th Chunk of size == 35. The final
    160   // state should look like this:
    161   // |0                                |35 (w)         |50
    162   // +---------------------------------+---------------+--------------------+
    163   // | Chunk 5                         | Padding Chunk | Chunk 4            |
    164   // +---------------------------------+---------------+--------------------+
    165 
    166   // Deletes all chunks from |wptr_| to |wptr_| + |record_size|.
    167   size_t padding_size = DeleteNextChunksFor(record_size);
    168 
    169   // Now first insert the new chunk. At the end, if necessary, add the padding.
    170   ChunkMeta::Key key(record);
    171   stats_.chunks_written++;
    172   stats_.bytes_written += size;
    173   auto it_and_inserted =
    174       index_.emplace(key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments,
    175                                     chunk_flags, producer_uid_trusted));
    176   if (PERFETTO_UNLIKELY(!it_and_inserted.second)) {
    177     // More likely a producer bug, but could also be a malicious producer.
    178     stats_.abi_violations++;
    179     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    180     index_.erase(it_and_inserted.first);
    181     index_.emplace(key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments,
    182                                   chunk_flags, producer_uid_trusted));
    183   }
    184   TRACE_BUFFER_DLOG("  copying @ [%lu - %lu] %zu", wptr_ - begin(),
    185                     wptr_ - begin() + record_size, record_size);
    186   WriteChunkRecord(record, src, size);
    187   TRACE_BUFFER_DLOG("Chunk raw: %s", HexDump(wptr_, record_size).c_str());
    188   wptr_ += record_size;
    189   if (wptr_ >= end()) {
    190     PERFETTO_DCHECK(padding_size == 0);
    191     wptr_ = begin();
    192     stats_.write_wrap_count++;
    193   }
    194   DcheckIsAlignedAndWithinBounds(wptr_);
    195 
    196   last_chunk_id_[std::make_pair(producer_id_trusted, writer_id)] = chunk_id;
    197 
    198   if (padding_size)
    199     AddPaddingRecord(padding_size);
    200 }
    201 
    202 size_t TraceBuffer::DeleteNextChunksFor(size_t bytes_to_clear) {
    203   // Find the position of the first chunk which begins at or after
    204   // (|wptr_| + |bytes|). Note that such a chunk might not exist and we might
    205   // either reach the end of the buffer or a zeroed region of the buffer.
    206   uint8_t* next_chunk_ptr = wptr_;
    207   uint8_t* search_end = wptr_ + bytes_to_clear;
    208   TRACE_BUFFER_DLOG("Delete [%zu %zu]", wptr_ - begin(), search_end - begin());
    209   DcheckIsAlignedAndWithinBounds(wptr_);
    210   PERFETTO_DCHECK(search_end <= end());
    211   while (next_chunk_ptr < search_end) {
    212     const ChunkRecord& next_chunk = *GetChunkRecordAt(next_chunk_ptr);
    213     TRACE_BUFFER_DLOG(
    214         "  scanning chunk [%zu %zu] (valid=%d)", next_chunk_ptr - begin(),
    215         next_chunk_ptr - begin() + next_chunk.size, next_chunk.is_valid());
    216 
    217     // We just reached the untouched part of the buffer, it's going to be all
    218     // zeroes from here to end().
    219     // TODO(primiano): optimization: if during Initialize() we fill the buffer
    220     // with padding records we could get rid of this branch.
    221     if (PERFETTO_UNLIKELY(!next_chunk.is_valid())) {
    222       // This should happen only at the first iteration. The zeroed area can
    223       // only begin precisely at the |wptr_|, not after. Otherwise it means that
    224       // we wrapped but screwed up the ChunkRecord chain.
    225       PERFETTO_DCHECK(next_chunk_ptr == wptr_);
    226       return 0;
    227     }
    228 
    229     // Remove |next_chunk| from the index, unless it's a padding record (padding
    230     // records are not part of the index).
    231     if (PERFETTO_LIKELY(!next_chunk.is_padding)) {
    232       ChunkMeta::Key key(next_chunk);
    233       auto it = index_.find(key);
    234       bool removed = false;
    235       if (PERFETTO_LIKELY(it != index_.end())) {
    236         const ChunkMeta& meta = it->second;
    237         if (PERFETTO_UNLIKELY(meta.num_fragments_read < meta.num_fragments))
    238           stats_.chunks_overwritten++;
    239         index_.erase(it);
    240         removed = true;
    241       }
    242       TRACE_BUFFER_DLOG("  del index {%" PRIu32 ",%" PRIu32
    243                         ",%u} @ [%lu - %lu] %zu",
    244                         key.producer_id, key.writer_id, key.chunk_id,
    245                         next_chunk_ptr - begin(),
    246                         next_chunk_ptr - begin() + next_chunk.size, removed);
    247       PERFETTO_DCHECK(removed);
    248     }
    249 
    250     next_chunk_ptr += next_chunk.size;
    251 
    252     // We should never hit this, unless we managed to screw up while writing
    253     // to the buffer and breaking the ChunkRecord(s) chain.
    254     // TODO(primiano): Write more meaningful logging with the status of the
    255     // buffer, to get more actionable bugs in case we hit this.
    256     PERFETTO_CHECK(next_chunk_ptr <= end());
    257   }
    258   PERFETTO_DCHECK(next_chunk_ptr >= search_end && next_chunk_ptr <= end());
    259   return static_cast<size_t>(next_chunk_ptr - search_end);
    260 }
    261 
    262 void TraceBuffer::AddPaddingRecord(size_t size) {
    263   PERFETTO_DCHECK(size >= sizeof(ChunkRecord) && size <= ChunkRecord::kMaxSize);
    264   ChunkRecord record(size);
    265   record.is_padding = 1;
    266   TRACE_BUFFER_DLOG("AddPaddingRecord @ [%lu - %lu] %zu", wptr_ - begin(),
    267                     wptr_ - begin() + size, size);
    268   WriteChunkRecord(record, nullptr, size - sizeof(ChunkRecord));
    269   // |wptr_| is deliberately not advanced when writing a padding record.
    270 }
    271 
    272 bool TraceBuffer::TryPatchChunkContents(ProducerID producer_id,
    273                                         WriterID writer_id,
    274                                         ChunkID chunk_id,
    275                                         const Patch* patches,
    276                                         size_t patches_size,
    277                                         bool other_patches_pending) {
    278   ChunkMeta::Key key(producer_id, writer_id, chunk_id);
    279   auto it = index_.find(key);
    280   if (it == index_.end()) {
    281     stats_.patches_failed++;
    282     return false;
    283   }
    284   ChunkMeta& chunk_meta = it->second;
    285 
    286   // Check that the index is consistent with the actual ProducerID/WriterID
    287   // stored in the ChunkRecord.
    288   PERFETTO_DCHECK(ChunkMeta::Key(*chunk_meta.chunk_record) == key);
    289   uint8_t* chunk_begin = reinterpret_cast<uint8_t*>(chunk_meta.chunk_record);
    290   PERFETTO_DCHECK(chunk_begin >= begin());
    291   uint8_t* chunk_end = chunk_begin + chunk_meta.chunk_record->size;
    292   PERFETTO_DCHECK(chunk_end <= end());
    293 
    294   static_assert(Patch::kSize == SharedMemoryABI::kPacketHeaderSize,
    295                 "Patch::kSize out of sync with SharedMemoryABI");
    296 
    297   for (size_t i = 0; i < patches_size; i++) {
    298     uint8_t* ptr =
    299         chunk_begin + sizeof(ChunkRecord) + patches[i].offset_untrusted;
    300     TRACE_BUFFER_DLOG("PatchChunk {%" PRIu32 ",%" PRIu32
    301                       ",%u} size=%zu @ %zu with {%02x %02x %02x %02x} cur "
    302                       "{%02x %02x %02x %02x}",
    303                       producer_id, writer_id, chunk_id, chunk_end - chunk_begin,
    304                       patches[i].offset_untrusted, patches[i].data[0],
    305                       patches[i].data[1], patches[i].data[2],
    306                       patches[i].data[3], ptr[0], ptr[1], ptr[2], ptr[3]);
    307     if (ptr < chunk_begin + sizeof(ChunkRecord) ||
    308         ptr > chunk_end - Patch::kSize) {
    309       // Either the IPC was so slow and in the meantime the writer managed to
    310       // wrap over |chunk_id| or the producer sent a malicious IPC.
    311       stats_.patches_failed++;
    312       return false;
    313     }
    314 
    315     // DCHECK that we are writing into a zero-filled size field and not into
    316     // valid data. It relies on ScatteredStreamWriter::ReserveBytes() to
    317     // zero-fill reservations in debug builds.
    318     char zero[Patch::kSize]{};
    319     PERFETTO_DCHECK(memcmp(ptr, &zero, Patch::kSize) == 0);
    320 
    321     memcpy(ptr, &patches[i].data[0], Patch::kSize);
    322   }
    323   TRACE_BUFFER_DLOG(
    324       "Chunk raw (after patch): %s",
    325       HexDump(chunk_begin, chunk_meta.chunk_record->size).c_str());
    326 
    327   stats_.patches_succeeded += patches_size;
    328   if (!other_patches_pending) {
    329     chunk_meta.flags &= ~kChunkNeedsPatching;
    330     chunk_meta.chunk_record->flags = chunk_meta.flags;
    331   }
    332   return true;
    333 }
    334 
    335 void TraceBuffer::BeginRead() {
    336   read_iter_ = GetReadIterForSequence(index_.begin());
    337 #if PERFETTO_DCHECK_IS_ON()
    338   changed_since_last_read_ = false;
    339 #endif
    340 }
    341 
    342 TraceBuffer::SequenceIterator TraceBuffer::GetReadIterForSequence(
    343     ChunkMap::iterator seq_begin) {
    344   SequenceIterator iter;
    345   iter.seq_begin = seq_begin;
    346   if (seq_begin == index_.end()) {
    347     iter.cur = iter.seq_end = index_.end();
    348     return iter;
    349   }
    350 
    351 #if PERFETTO_DCHECK_IS_ON()
    352   // Either |seq_begin| is == index_.begin() or the item immediately before must
    353   // belong to a different {ProducerID, WriterID} sequence.
    354   if (seq_begin != index_.begin() && seq_begin != index_.end()) {
    355     auto prev_it = seq_begin;
    356     prev_it--;
    357     PERFETTO_DCHECK(
    358         seq_begin == index_.begin() ||
    359         std::tie(prev_it->first.producer_id, prev_it->first.writer_id) <
    360             std::tie(seq_begin->first.producer_id, seq_begin->first.writer_id));
    361   }
    362 #endif
    363 
    364   // Find the first entry that has a greater {ProducerID, WriterID} (or just
    365   // index_.end() if we reached the end).
    366   ChunkMeta::Key key = seq_begin->first;  // Deliberate copy.
    367   key.chunk_id = kMaxChunkID;
    368   iter.seq_end = index_.upper_bound(key);
    369   PERFETTO_DCHECK(iter.seq_begin != iter.seq_end);
    370 
    371   // Now find the first entry between [seq_begin, seq_end) that is
    372   // > last_chunk_id_. This is where we the sequence will start (see notes about
    373   // wrapping in the header).
    374   auto producer_and_writer_id = std::make_pair(key.producer_id, key.writer_id);
    375   PERFETTO_DCHECK(last_chunk_id_.count(producer_and_writer_id));
    376   iter.wrapping_id = last_chunk_id_[producer_and_writer_id];
    377   key.chunk_id = iter.wrapping_id;
    378   iter.cur = index_.upper_bound(key);
    379   if (iter.cur == iter.seq_end)
    380     iter.cur = iter.seq_begin;
    381   return iter;
    382 }
    383 
    384 void TraceBuffer::SequenceIterator::MoveNext() {
    385   // Note: |seq_begin| might be == |seq_end|.
    386   if (cur == seq_end || cur->first.chunk_id == wrapping_id) {
    387     cur = seq_end;
    388     return;
    389   }
    390   if (++cur == seq_end)
    391     cur = seq_begin;
    392 }
    393 
    394 bool TraceBuffer::ReadNextTracePacket(TracePacket* packet,
    395                                       uid_t* producer_uid) {
    396   // Note: MoveNext() moves only within the next chunk within the same
    397   // {ProducerID, WriterID} sequence. Here we want to:
    398   // - return the next patched+complete packet in the current sequence, if any.
    399   // - return the first patched+complete packet in the next sequence, if any.
    400   // - return false if none of the above is found.
    401   TRACE_BUFFER_DLOG("ReadNextTracePacket()");
    402 
    403   // Just in case we forget to initialize it below.
    404   *producer_uid = kInvalidUid;
    405 
    406 #if PERFETTO_DCHECK_IS_ON()
    407   PERFETTO_DCHECK(!changed_since_last_read_);
    408 #endif
    409   for (;; read_iter_.MoveNext()) {
    410     if (PERFETTO_UNLIKELY(!read_iter_.is_valid())) {
    411       // We ran out of chunks in the current {ProducerID, WriterID} sequence or
    412       // we just reached the index_.end().
    413 
    414       if (PERFETTO_UNLIKELY(read_iter_.seq_end == index_.end()))
    415         return false;
    416 
    417       // We reached the end of sequence, move to the next one.
    418       // Note: ++read_iter_.seq_end might become index_.end(), but
    419       // GetReadIterForSequence() knows how to deal with that.
    420       read_iter_ = GetReadIterForSequence(read_iter_.seq_end);
    421       PERFETTO_DCHECK(read_iter_.is_valid() && read_iter_.cur != index_.end());
    422     }
    423 
    424     ChunkMeta* chunk_meta = &*read_iter_;
    425 
    426     // If the chunk has holes that are awaiting to be patched out-of-band,
    427     // skip the current sequence and move to the next one.
    428     if (chunk_meta->flags & kChunkNeedsPatching) {
    429       read_iter_.MoveToEnd();
    430       continue;
    431     }
    432 
    433     const uid_t trusted_uid = chunk_meta->trusted_uid;
    434 
    435     // At this point we have a chunk in |chunk_meta| that has not been fully
    436     // read. We don't know yet whether we have enough data to read the full
    437     // packet (in the case it's fragmented over several chunks) and we are about
    438     // to find that out. Specifically:
    439     // A) If the first fragment is unread and is a fragment continuing from a
    440     //    previous chunk, it means we have missed the previous ChunkID. In
    441     //    fact, if this wasn't the case, a previous call to ReadNext() shouldn't
    442     //    have moved the cursor to this chunk.
    443     // B) Any fragment > 0 && < last is always readable. By definition an inner
    444     //    packet is never fragmented and hence doesn't require neither stitching
    445     //    nor any out-of-band patching. The same applies to the last packet
    446     //    iff it doesn't continue on the next chunk.
    447     // C) If the last packet (which might be also the only packet in the chunk)
    448     //    is a fragment and continues on the next chunk, we peek at the next
    449     //    chunks and, if we have all of them, mark as read and move the cursor.
    450     //
    451     // +---------------+   +-------------------+  +---------------+
    452     // | ChunkID: 1    |   | ChunkID: 2        |  | ChunkID: 3    |
    453     // |---------------+   +-------------------+  +---------------+
    454     // | Packet 1      |   |                   |  | ... Packet 3  |
    455     // | Packet 2      |   | ... Packet 3  ... |  | Packet 4      |
    456     // | Packet 3  ... |   |                   |  | Packet 5 ...  |
    457     // +---------------+   +-------------------+  +---------------+
    458 
    459     PERFETTO_DCHECK(chunk_meta->num_fragments_read <=
    460                     chunk_meta->num_fragments);
    461     while (chunk_meta->num_fragments_read < chunk_meta->num_fragments) {
    462       enum { kSkip = 0, kReadOnePacket, kTryReadAhead } action;
    463       if (chunk_meta->num_fragments_read == 0) {
    464         if (chunk_meta->flags & kFirstPacketContinuesFromPrevChunk) {
    465           action = kSkip;  // Case A.
    466         } else if (chunk_meta->num_fragments == 1 &&
    467                    (chunk_meta->flags & kLastPacketContinuesOnNextChunk)) {
    468           action = kTryReadAhead;  // Case C.
    469         } else {
    470           action = kReadOnePacket;  // Case B.
    471         }
    472       } else if (chunk_meta->num_fragments_read <
    473                      chunk_meta->num_fragments - 1 ||
    474                  !(chunk_meta->flags & kLastPacketContinuesOnNextChunk)) {
    475         action = kReadOnePacket;  // Case B.
    476       } else {
    477         action = kTryReadAhead;  // Case C.
    478       }
    479 
    480       TRACE_BUFFER_DLOG("  chunk %u, packet %hu of %hu, action=%d",
    481                         read_iter_.chunk_id(), chunk_meta->num_fragments_read,
    482                         chunk_meta->num_fragments, action);
    483 
    484       if (action == kSkip) {
    485         // This fragment will be skipped forever, not just in this ReadPacket()
    486         // iteration. This happens by virtue of ReadNextPacketInChunk()
    487         // incrementing the |num_fragments_read| and marking the fragment as
    488         // read even if we didn't really.
    489         ReadNextPacketInChunk(chunk_meta, nullptr);
    490         continue;
    491       }
    492 
    493       if (action == kReadOnePacket) {
    494         // The easy peasy case B.
    495         if (PERFETTO_LIKELY(ReadNextPacketInChunk(chunk_meta, packet))) {
    496           *producer_uid = trusted_uid;
    497           return true;
    498         }
    499 
    500         // In extremely rare cases (producer bugged / malicious) the chunk might
    501         // contain an invalid fragment. In such case we don't want to stall the
    502         // sequence but just skip the chunk and move on.
    503         stats_.abi_violations++;
    504         PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    505         break;
    506       }
    507 
    508       PERFETTO_DCHECK(action == kTryReadAhead);
    509       ReadAheadResult ra_res = ReadAhead(packet);
    510       if (ra_res == ReadAheadResult::kSucceededReturnSlices) {
    511         stats_.readaheads_succeeded++;
    512         *producer_uid = trusted_uid;
    513         return true;
    514       }
    515 
    516       if (ra_res == ReadAheadResult::kFailedMoveToNextSequence) {
    517         // readahead didn't find a contigous packet sequence. We'll try again
    518         // on the next ReadPacket() call.
    519         stats_.readaheads_failed++;
    520 
    521         // TODO(primiano): optimization: this MoveToEnd() is the reason why
    522         // MoveNext() (that is called in the outer for(;;MoveNext)) needs to
    523         // deal gracefully with the case of |cur|==|seq_end|. Maybe we can do
    524         // something to avoid that check by reshuffling the code here?
    525         read_iter_.MoveToEnd();
    526 
    527         // This break will go back to beginning of the for(;;MoveNext()). That
    528         // will move to the next sequence because we set the read iterator to
    529         // its end.
    530         break;
    531       }
    532 
    533       PERFETTO_DCHECK(ra_res == ReadAheadResult::kFailedStayOnSameSequence);
    534 
    535       // In this case ReadAhead() might advance |read_iter_|, so we need to
    536       // re-cache the |chunk_meta| pointer to point to the current chunk.
    537       chunk_meta = &*read_iter_;
    538     }  // while(...)  [iterate over packet fragments for the current chunk].
    539   }    // for(;;MoveNext()) [iterate over chunks].
    540 }
    541 
    542 TraceBuffer::ReadAheadResult TraceBuffer::ReadAhead(TracePacket* packet) {
    543   static_assert(static_cast<ChunkID>(kMaxChunkID + 1) == 0,
    544                 "relying on kMaxChunkID to wrap naturally");
    545   TRACE_BUFFER_DLOG(" readahead start @ chunk %u", read_iter_.chunk_id());
    546   ChunkID next_chunk_id = read_iter_.chunk_id() + 1;
    547   SequenceIterator it = read_iter_;
    548   for (it.MoveNext(); it.is_valid(); it.MoveNext(), next_chunk_id++) {
    549     // We should stay within the same sequence while iterating here.
    550     PERFETTO_DCHECK(it.producer_id() == read_iter_.producer_id() &&
    551                     it.writer_id() == read_iter_.writer_id());
    552 
    553     TRACE_BUFFER_DLOG("   expected chunk ID: %u, actual ID: %u", next_chunk_id,
    554                       it.chunk_id());
    555 
    556     if (PERFETTO_UNLIKELY((*it).num_fragments == 0))
    557       continue;
    558 
    559     // If we miss the next chunk, stop looking in the current sequence and
    560     // try another sequence. This chunk might come in the near future.
    561     // The second condition is the edge case of a buggy/malicious
    562     // producer. The ChunkID is contiguous but its flags don't make sense.
    563     if (it.chunk_id() != next_chunk_id ||
    564         PERFETTO_UNLIKELY(
    565             !((*it).flags & kFirstPacketContinuesFromPrevChunk))) {
    566       return ReadAheadResult::kFailedMoveToNextSequence;
    567     }
    568 
    569     // If the chunk is contiguous but has not been patched yet move to the next
    570     // sequence and try coming back here on the next ReadNextTracePacket() call.
    571     // TODO(primiano): add a test to cover this, it's a subtle case.
    572     if ((*it).flags & kChunkNeedsPatching)
    573       return ReadAheadResult::kFailedMoveToNextSequence;
    574 
    575     // This is the case of an intermediate chunk which contains only one
    576     // fragment which continues on the next chunk. This is the case for large
    577     // packets, e.g.: [Packet0, Packet1(0)] [Packet1(1)] [Packet1(2), ...]
    578     // (Packet1(X) := fragment X of Packet1).
    579     if ((*it).num_fragments == 1 &&
    580         ((*it).flags & kLastPacketContinuesOnNextChunk)) {
    581       continue;
    582     }
    583 
    584     // We made it! We got all fragments for the packet without holes.
    585     TRACE_BUFFER_DLOG("  readahead success @ chunk %u", it.chunk_id());
    586     PERFETTO_DCHECK(((*it).num_fragments == 1 &&
    587                      !((*it).flags & kLastPacketContinuesOnNextChunk)) ||
    588                     (*it).num_fragments > 1);
    589 
    590     // Now let's re-iterate over the [read_iter_, it] sequence and mark
    591     // all the fragments as read.
    592     bool packet_corruption = false;
    593     for (;;) {
    594       PERFETTO_DCHECK(read_iter_.is_valid());
    595       TRACE_BUFFER_DLOG("    commit chunk %u", read_iter_.chunk_id());
    596       if (PERFETTO_LIKELY((*read_iter_).num_fragments > 0)) {
    597         // In the unlikely case of a corrupted packet, invalidate the all
    598         // stitching and move on to the next chunk in the same sequence,
    599         // if any.
    600         packet_corruption |= !ReadNextPacketInChunk(&*read_iter_, packet);
    601       }
    602       if (read_iter_.cur == it.cur)
    603         break;
    604       read_iter_.MoveNext();
    605     }  // for(;;)
    606     PERFETTO_DCHECK(read_iter_.cur == it.cur);
    607 
    608     if (PERFETTO_UNLIKELY(packet_corruption)) {
    609       stats_.abi_violations++;
    610       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    611       *packet = TracePacket();  // clear.
    612       return ReadAheadResult::kFailedStayOnSameSequence;
    613     }
    614 
    615     return ReadAheadResult::kSucceededReturnSlices;
    616   }  // for(it...)  [readahead loop]
    617   return ReadAheadResult::kFailedMoveToNextSequence;
    618 }
    619 
    620 bool TraceBuffer::ReadNextPacketInChunk(ChunkMeta* chunk_meta,
    621                                         TracePacket* packet) {
    622   PERFETTO_DCHECK(chunk_meta->num_fragments_read < chunk_meta->num_fragments);
    623   PERFETTO_DCHECK(!(chunk_meta->flags & kChunkNeedsPatching));
    624 
    625   const uint8_t* record_begin =
    626       reinterpret_cast<const uint8_t*>(chunk_meta->chunk_record);
    627   const uint8_t* record_end = record_begin + chunk_meta->chunk_record->size;
    628   const uint8_t* packets_begin = record_begin + sizeof(ChunkRecord);
    629   const uint8_t* packet_begin = packets_begin + chunk_meta->cur_fragment_offset;
    630 
    631   if (PERFETTO_UNLIKELY(packet_begin < packets_begin ||
    632                         packet_begin >= record_end)) {
    633     // The producer has a bug or is malicious and did declare that the chunk
    634     // contains more packets beyond its boundaries.
    635     stats_.abi_violations++;
    636     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    637     return false;
    638   }
    639 
    640   // A packet (or a fragment) starts with a varint stating its size, followed
    641   // by its content. The varint shouldn't be larger than 4 bytes (just in case
    642   // the producer is using a redundant encoding)
    643   uint64_t packet_size = 0;
    644   const uint8_t* header_end =
    645       std::min(packet_begin + protozero::proto_utils::kMessageLengthFieldSize,
    646                record_end);
    647   const uint8_t* packet_data = protozero::proto_utils::ParseVarInt(
    648       packet_begin, header_end, &packet_size);
    649 
    650   const uint8_t* next_packet = packet_data + packet_size;
    651   if (PERFETTO_UNLIKELY(next_packet <= packet_begin ||
    652                         next_packet > record_end)) {
    653     stats_.abi_violations++;
    654     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    655     chunk_meta->cur_fragment_offset = 0;
    656     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
    657     return false;
    658   }
    659   chunk_meta->cur_fragment_offset =
    660       static_cast<uint16_t>(next_packet - packets_begin);
    661   chunk_meta->num_fragments_read++;
    662 
    663   if (PERFETTO_UNLIKELY(packet_size == 0)) {
    664     stats_.abi_violations++;
    665     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
    666     return false;
    667   }
    668 
    669   if (PERFETTO_LIKELY(packet))
    670     packet->AddSlice(packet_data, static_cast<size_t>(packet_size));
    671 
    672   return true;
    673 }
    674 
    675 }  // namespace perfetto
    676