1//===- FDRRecordProducer.cpp - XRay FDR Mode Record Producer --------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8#include "llvm/XRay/FDRRecordProducer.h"
9#include "llvm/Support/DataExtractor.h"
10
11#include <cstdint>
12
13namespace llvm {
14namespace xray {
15
16namespace {
17
18// Keep this in sync with the values written in the XRay FDR mode runtime in
19// compiler-rt.
20enum MetadataRecordKinds : uint8_t {
21 NewBufferKind,
22 EndOfBufferKind,
23 NewCPUIdKind,
24 TSCWrapKind,
25 WalltimeMarkerKind,
26 CustomEventMarkerKind,
27 CallArgumentKind,
28 BufferExtentsKind,
29 TypedEventMarkerKind,
30 PidKind,
31 // This is an end marker, used to identify the upper bound for this enum.
32 EnumEndMarker,
33};
34
35Expected<std::unique_ptr<Record>>
36metadataRecordType(const XRayFileHeader &Header, uint8_t T) {
37
38 if (T >= static_cast<uint8_t>(MetadataRecordKinds::EnumEndMarker))
39 return createStringError(EC: std::make_error_code(e: std::errc::invalid_argument),
40 Fmt: "Invalid metadata record type: %d", Vals: T);
41 switch (T) {
42 case MetadataRecordKinds::NewBufferKind:
43 return std::make_unique<NewBufferRecord>();
44 case MetadataRecordKinds::EndOfBufferKind:
45 if (Header.Version >= 2)
46 return createStringError(
47 EC: std::make_error_code(e: std::errc::executable_format_error),
48 S: "End of buffer records are no longer supported starting version "
49 "2 of the log.");
50 return std::make_unique<EndBufferRecord>();
51 case MetadataRecordKinds::NewCPUIdKind:
52 return std::make_unique<NewCPUIDRecord>();
53 case MetadataRecordKinds::TSCWrapKind:
54 return std::make_unique<TSCWrapRecord>();
55 case MetadataRecordKinds::WalltimeMarkerKind:
56 return std::make_unique<WallclockRecord>();
57 case MetadataRecordKinds::CustomEventMarkerKind:
58 if (Header.Version >= 5)
59 return std::make_unique<CustomEventRecordV5>();
60 return std::make_unique<CustomEventRecord>();
61 case MetadataRecordKinds::CallArgumentKind:
62 return std::make_unique<CallArgRecord>();
63 case MetadataRecordKinds::BufferExtentsKind:
64 return std::make_unique<BufferExtents>();
65 case MetadataRecordKinds::TypedEventMarkerKind:
66 return std::make_unique<TypedEventRecord>();
67 case MetadataRecordKinds::PidKind:
68 return std::make_unique<PIDRecord>();
69 case MetadataRecordKinds::EnumEndMarker:
70 llvm_unreachable("Invalid MetadataRecordKind");
71 }
72 llvm_unreachable("Unhandled MetadataRecordKinds enum value");
73}
74
75constexpr bool isMetadataIntroducer(uint8_t FirstByte) {
76 return FirstByte & 0x01u;
77}
78
79} // namespace
80
81Expected<std::unique_ptr<Record>>
82FileBasedRecordProducer::findNextBufferExtent() {
83 // We seek one byte at a time until we find a suitable buffer extents metadata
84 // record introducer.
85 std::unique_ptr<Record> R;
86 while (!R) {
87 auto PreReadOffset = OffsetPtr;
88 uint8_t FirstByte = E.getU8(offset_ptr: &OffsetPtr);
89 if (OffsetPtr == PreReadOffset)
90 return createStringError(
91 EC: std::make_error_code(e: std::errc::executable_format_error),
92 Fmt: "Failed reading one byte from offset %" PRId64 ".", Vals: OffsetPtr);
93
94 if (isMetadataIntroducer(FirstByte)) {
95 auto LoadedType = FirstByte >> 1;
96 if (LoadedType == MetadataRecordKinds::BufferExtentsKind) {
97 auto MetadataRecordOrErr = metadataRecordType(Header, T: LoadedType);
98 if (!MetadataRecordOrErr)
99 return MetadataRecordOrErr.takeError();
100
101 R = std::move(MetadataRecordOrErr.get());
102 RecordInitializer RI(E, OffsetPtr);
103 if (auto Err = R->apply(V&: RI))
104 return std::move(Err);
105 return std::move(R);
106 }
107 }
108 }
109 llvm_unreachable("Must always terminate with either an error or a record.");
110}
111
112Expected<std::unique_ptr<Record>> FileBasedRecordProducer::produce() {
113 // First, we set up our result record.
114 std::unique_ptr<Record> R;
115
116 // Before we do any further reading, we should check whether we're at the end
117 // of the current buffer we're been consuming. In FDR logs version >= 3, we
118 // rely on the buffer extents record to determine how many bytes we should be
119 // considering as valid records.
120 if (Header.Version >= 3 && CurrentBufferBytes == 0) {
121 // Find the next buffer extents record.
122 auto BufferExtentsOrError = findNextBufferExtent();
123 if (!BufferExtentsOrError)
124 return joinErrors(
125 E1: BufferExtentsOrError.takeError(),
126 E2: createStringError(
127 EC: std::make_error_code(e: std::errc::executable_format_error),
128 S: "Failed to find the next BufferExtents record."));
129
130 R = std::move(BufferExtentsOrError.get());
131 assert(R != nullptr);
132 assert(isa<BufferExtents>(R.get()));
133 auto BE = cast<BufferExtents>(Val: R.get());
134 CurrentBufferBytes = BE->size();
135 return std::move(R);
136 }
137
138 //
139 // At the top level, we read one byte to determine the type of the record to
140 // create. This byte will comprise of the following bits:
141 //
142 // - offset 0: A '1' indicates a metadata record, a '0' indicates a function
143 // record.
144 // - offsets 1-7: For metadata records, this will indicate the kind of
145 // metadata record should be loaded.
146 //
147 // We read first byte, then create the appropriate type of record to consume
148 // the rest of the bytes.
149 auto PreReadOffset = OffsetPtr;
150 uint8_t FirstByte = E.getU8(offset_ptr: &OffsetPtr);
151 if (OffsetPtr == PreReadOffset)
152 return createStringError(
153 EC: std::make_error_code(e: std::errc::executable_format_error),
154 Fmt: "Failed reading one byte from offset %" PRId64 ".", Vals: OffsetPtr);
155
156 // For metadata records, handle especially here.
157 if (isMetadataIntroducer(FirstByte)) {
158 auto LoadedType = FirstByte >> 1;
159 auto MetadataRecordOrErr = metadataRecordType(Header, T: LoadedType);
160 if (!MetadataRecordOrErr)
161 return joinErrors(
162 E1: MetadataRecordOrErr.takeError(),
163 E2: createStringError(
164 EC: std::make_error_code(e: std::errc::executable_format_error),
165 Fmt: "Encountered an unsupported metadata record (%d) "
166 "at offset %" PRId64 ".",
167 Vals: LoadedType, Vals: PreReadOffset));
168 R = std::move(MetadataRecordOrErr.get());
169 } else {
170 R = std::make_unique<FunctionRecord>();
171 }
172 RecordInitializer RI(E, OffsetPtr);
173
174 if (auto Err = R->apply(V&: RI))
175 return std::move(Err);
176
177 // If we encountered a BufferExtents record, we should record the remaining
178 // bytes for the current buffer, to determine when we should start ignoring
179 // potentially malformed data and looking for buffer extents records.
180 if (auto BE = dyn_cast<BufferExtents>(Val: R.get())) {
181 CurrentBufferBytes = BE->size();
182 } else if (Header.Version >= 3) {
183 if (OffsetPtr - PreReadOffset > CurrentBufferBytes)
184 return createStringError(
185 EC: std::make_error_code(e: std::errc::executable_format_error),
186 Fmt: "Buffer over-read at offset %" PRId64 " (over-read by %" PRId64
187 " bytes); Record Type = %s.",
188 Vals: OffsetPtr, Vals: (OffsetPtr - PreReadOffset) - CurrentBufferBytes,
189 Vals: Record::kindToString(K: R->getRecordType()).data());
190
191 CurrentBufferBytes -= OffsetPtr - PreReadOffset;
192 }
193 assert(R != nullptr);
194 return std::move(R);
195}
196
197} // namespace xray
198} // namespace llvm
199