| //===- FDRRecordProducer.cpp - XRay FDR Mode Record Producer --------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/XRay/FDRRecordProducer.h" | |
| #include "llvm/Support/DataExtractor.h" | |
| #include <cstdint> | |
| namespace llvm { | |
| namespace xray { | |
| namespace { | |
| // Keep this in sync with the values written in the XRay FDR mode runtime in | |
| // compiler-rt. | |
| enum MetadataRecordKinds : uint8_t { | |
| NewBufferKind, | |
| EndOfBufferKind, | |
| NewCPUIdKind, | |
| TSCWrapKind, | |
| WalltimeMarkerKind, | |
| CustomEventMarkerKind, | |
| CallArgumentKind, | |
| BufferExtentsKind, | |
| TypedEventMarkerKind, | |
| PidKind, | |
| // This is an end marker, used to identify the upper bound for this enum. | |
| EnumEndMarker, | |
| }; | |
| Expected<std::unique_ptr<Record>> | |
| metadataRecordType(const XRayFileHeader &Header, uint8_t T) { | |
| if (T >= static_cast<uint8_t>(MetadataRecordKinds::EnumEndMarker)) | |
| return createStringError(std::make_error_code(std::errc::invalid_argument), | |
| "Invalid metadata record type: %d", T); | |
| switch (T) { | |
| case MetadataRecordKinds::NewBufferKind: | |
| return std::make_unique<NewBufferRecord>(); | |
| case MetadataRecordKinds::EndOfBufferKind: | |
| if (Header.Version >= 2) | |
| return createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "End of buffer records are no longer supported starting version " | |
| "2 of the log."); | |
| return std::make_unique<EndBufferRecord>(); | |
| case MetadataRecordKinds::NewCPUIdKind: | |
| return std::make_unique<NewCPUIDRecord>(); | |
| case MetadataRecordKinds::TSCWrapKind: | |
| return std::make_unique<TSCWrapRecord>(); | |
| case MetadataRecordKinds::WalltimeMarkerKind: | |
| return std::make_unique<WallclockRecord>(); | |
| case MetadataRecordKinds::CustomEventMarkerKind: | |
| if (Header.Version >= 5) | |
| return std::make_unique<CustomEventRecordV5>(); | |
| return std::make_unique<CustomEventRecord>(); | |
| case MetadataRecordKinds::CallArgumentKind: | |
| return std::make_unique<CallArgRecord>(); | |
| case MetadataRecordKinds::BufferExtentsKind: | |
| return std::make_unique<BufferExtents>(); | |
| case MetadataRecordKinds::TypedEventMarkerKind: | |
| return std::make_unique<TypedEventRecord>(); | |
| case MetadataRecordKinds::PidKind: | |
| return std::make_unique<PIDRecord>(); | |
| case MetadataRecordKinds::EnumEndMarker: | |
| llvm_unreachable("Invalid MetadataRecordKind"); | |
| } | |
| llvm_unreachable("Unhandled MetadataRecordKinds enum value"); | |
| } | |
| constexpr bool isMetadataIntroducer(uint8_t FirstByte) { | |
| return FirstByte & 0x01u; | |
| } | |
| } // namespace | |
| Expected<std::unique_ptr<Record>> | |
| FileBasedRecordProducer::findNextBufferExtent() { | |
| // We seek one byte at a time until we find a suitable buffer extents metadata | |
| // record introducer. | |
| std::unique_ptr<Record> R; | |
| while (!R) { | |
| auto PreReadOffset = OffsetPtr; | |
| uint8_t FirstByte = E.getU8(&OffsetPtr); | |
| if (OffsetPtr == PreReadOffset) | |
| return createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "Failed reading one byte from offset %" PRId64 ".", OffsetPtr); | |
| if (isMetadataIntroducer(FirstByte)) { | |
| auto LoadedType = FirstByte >> 1; | |
| if (LoadedType == MetadataRecordKinds::BufferExtentsKind) { | |
| auto MetadataRecordOrErr = metadataRecordType(Header, LoadedType); | |
| if (!MetadataRecordOrErr) | |
| return MetadataRecordOrErr.takeError(); | |
| R = std::move(MetadataRecordOrErr.get()); | |
| RecordInitializer RI(E, OffsetPtr); | |
| if (auto Err = R->apply(RI)) | |
| return std::move(Err); | |
| return std::move(R); | |
| } | |
| } | |
| } | |
| llvm_unreachable("Must always terminate with either an error or a record."); | |
| } | |
| Expected<std::unique_ptr<Record>> FileBasedRecordProducer::produce() { | |
| // First, we set up our result record. | |
| std::unique_ptr<Record> R; | |
| // Before we do any further reading, we should check whether we're at the end | |
| // of the current buffer we're been consuming. In FDR logs version >= 3, we | |
| // rely on the buffer extents record to determine how many bytes we should be | |
| // considering as valid records. | |
| if (Header.Version >= 3 && CurrentBufferBytes == 0) { | |
| // Find the next buffer extents record. | |
| auto BufferExtentsOrError = findNextBufferExtent(); | |
| if (!BufferExtentsOrError) | |
| return joinErrors( | |
| BufferExtentsOrError.takeError(), | |
| createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "Failed to find the next BufferExtents record.")); | |
| R = std::move(BufferExtentsOrError.get()); | |
| assert(R != nullptr); | |
| assert(isa<BufferExtents>(R.get())); | |
| auto BE = cast<BufferExtents>(R.get()); | |
| CurrentBufferBytes = BE->size(); | |
| return std::move(R); | |
| } | |
| // | |
| // At the top level, we read one byte to determine the type of the record to | |
| // create. This byte will comprise of the following bits: | |
| // | |
| // - offset 0: A '1' indicates a metadata record, a '0' indicates a function | |
| // record. | |
| // - offsets 1-7: For metadata records, this will indicate the kind of | |
| // metadata record should be loaded. | |
| // | |
| // We read first byte, then create the appropriate type of record to consume | |
| // the rest of the bytes. | |
| auto PreReadOffset = OffsetPtr; | |
| uint8_t FirstByte = E.getU8(&OffsetPtr); | |
| if (OffsetPtr == PreReadOffset) | |
| return createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "Failed reading one byte from offset %" PRId64 ".", OffsetPtr); | |
| // For metadata records, handle especially here. | |
| if (isMetadataIntroducer(FirstByte)) { | |
| auto LoadedType = FirstByte >> 1; | |
| auto MetadataRecordOrErr = metadataRecordType(Header, LoadedType); | |
| if (!MetadataRecordOrErr) | |
| return joinErrors( | |
| MetadataRecordOrErr.takeError(), | |
| createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "Encountered an unsupported metadata record (%d) " | |
| "at offset %" PRId64 ".", | |
| LoadedType, PreReadOffset)); | |
| R = std::move(MetadataRecordOrErr.get()); | |
| } else { | |
| R = std::make_unique<FunctionRecord>(); | |
| } | |
| RecordInitializer RI(E, OffsetPtr); | |
| if (auto Err = R->apply(RI)) | |
| return std::move(Err); | |
| // If we encountered a BufferExtents record, we should record the remaining | |
| // bytes for the current buffer, to determine when we should start ignoring | |
| // potentially malformed data and looking for buffer extents records. | |
| if (auto BE = dyn_cast<BufferExtents>(R.get())) { | |
| CurrentBufferBytes = BE->size(); | |
| } else if (Header.Version >= 3) { | |
| if (OffsetPtr - PreReadOffset > CurrentBufferBytes) | |
| return createStringError( | |
| std::make_error_code(std::errc::executable_format_error), | |
| "Buffer over-read at offset %" PRId64 " (over-read by %" PRId64 | |
| " bytes); Record Type = %s.", | |
| OffsetPtr, (OffsetPtr - PreReadOffset) - CurrentBufferBytes, | |
| Record::kindToString(R->getRecordType()).data()); | |
| CurrentBufferBytes -= OffsetPtr - PreReadOffset; | |
| } | |
| assert(R != nullptr); | |
| return std::move(R); | |
| } | |
| } // namespace xray | |
| } // namespace llvm |