1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 18:54:02 +01:00

[XRay][compiler-rt] Add PID field to llvm-xray tool and add PID metadata record entry in FDR mode

Summary:
llvm-xray changes:
- account-mode - process-id  {...} shows after thread-id
- convert-mode - process {...} shows after thread
- parses FDR and basic mode pid entries
- Checks version number for FDR log parsing.

Basic logging changes:
- Update header version from 2 -> 3

FDR logging changes:
- Update header version from 2 -> 3
- in writeBufferPreamble, there is an additional PID Metadata record (after thread id record and tsc record)

Test cases changes:
- fdr-mode.cc, fdr-single-thread.cc, fdr-thread-order.cc modified to catch process id output in the log.

Reviewers: dberris

Reviewed By: dberris

Subscribers: hiraditya, llvm-commits, #sanitizers

Differential Revision: https://reviews.llvm.org/D49153

llvm-svn: 336974
This commit is contained in:
Dean Michael Berris 2018-07-13 05:38:22 +00:00
parent 64e43783ed
commit aab4e8c7ac
13 changed files with 169 additions and 29 deletions

View File

@ -75,6 +75,9 @@ struct XRayRecord {
/// The thread ID for the currently running thread.
uint32_t TId;
/// The process ID for the currently running process.
uint32_t PId;
/// The function call arguments.
std::vector<uint64_t> CallArgs;
};

View File

@ -37,6 +37,7 @@ struct YAMLXRayRecord {
std::string Function;
uint64_t TSC;
uint32_t TId;
uint32_t PId;
std::vector<uint64_t> CallArgs;
};
@ -79,6 +80,7 @@ template <> struct MappingTraits<xray::YAMLXRayRecord> {
IO.mapOptional("args", Record.CallArgs);
IO.mapRequired("cpu", Record.CPU);
IO.mapRequired("thread", Record.TId);
IO.mapOptional("process", Record.PId, 0U);
IO.mapRequired("kind", Record.Type);
IO.mapRequired("tsc", Record.TSC);
}

View File

@ -48,7 +48,8 @@ Error readBinaryFormatHeader(StringRef Data, XRayFileHeader &FileHeader) {
FileHeader.NonstopTSC = Bitfield & 1uL << 1;
FileHeader.CycleFrequency = HeaderExtractor.getU64(&OffsetPtr);
std::memcpy(&FileHeader.FreeFormData, Data.bytes_begin() + OffsetPtr, 16);
if (FileHeader.Version != 1 && FileHeader.Version != 2)
if (FileHeader.Version != 1 && FileHeader.Version != 2 &&
FileHeader.Version != 3)
return make_error<StringError>(
Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version),
std::make_error_code(std::errc::invalid_argument));
@ -78,7 +79,8 @@ Error loadNaiveFormatLog(StringRef Data, XRayFileHeader &FileHeader,
// (4) sint32 : function id
// (8) uint64 : tsc
// (4) uint32 : thread id
// (12) - : padding
// (4) uint32 : process id
// (8) - : padding
for (auto S = Data.drop_front(32); !S.empty(); S = S.drop_front(32)) {
DataExtractor RecordExtractor(S, true, 8);
uint32_t OffsetPtr = 0;
@ -110,6 +112,7 @@ Error loadNaiveFormatLog(StringRef Data, XRayFileHeader &FileHeader,
Record.FuncId = RecordExtractor.getSigned(&OffsetPtr, sizeof(int32_t));
Record.TSC = RecordExtractor.getU64(&OffsetPtr);
Record.TId = RecordExtractor.getU32(&OffsetPtr);
Record.PId = RecordExtractor.getU32(&OffsetPtr);
break;
}
case 1: { // Arg payload record.
@ -118,15 +121,18 @@ Error loadNaiveFormatLog(StringRef Data, XRayFileHeader &FileHeader,
OffsetPtr += 2;
int32_t FuncId = RecordExtractor.getSigned(&OffsetPtr, sizeof(int32_t));
auto TId = RecordExtractor.getU32(&OffsetPtr);
if (Record.FuncId != FuncId || Record.TId != TId)
auto PId = RecordExtractor.getU32(&OffsetPtr);
// Make a check for versions above 3 for the Pid field
if (Record.FuncId != FuncId || Record.TId != TId ||
(FileHeader.Version >= 3 ? Record.PId != PId : false))
return make_error<StringError>(
Twine("Corrupted log, found arg payload following non-matching "
"function + thread record. Record for function ") +
Twine(Record.FuncId) + " != " + Twine(FuncId) + "; offset: " +
Twine(S.data() - Data.data()),
std::make_error_code(std::errc::executable_format_error));
// Advance another four bytes to avoid padding.
OffsetPtr += 4;
auto Arg = RecordExtractor.getU64(&OffsetPtr);
Record.CallArgs.push_back(Arg);
break;
@ -148,6 +154,7 @@ Error loadNaiveFormatLog(StringRef Data, XRayFileHeader &FileHeader,
struct FDRState {
uint16_t CPUId;
uint16_t ThreadId;
int32_t ProcessId;
uint64_t BaseTSC;
/// Encode some of the state transitions for the FDR log reader as explicit
@ -161,6 +168,7 @@ struct FDRState {
CUSTOM_EVENT_DATA,
CALL_ARGUMENT,
BUFFER_EXTENTS,
PID_RECORD,
};
Token Expects;
@ -188,6 +196,8 @@ const char *fdrStateToTwine(const FDRState::Token &state) {
return "CALL_ARGUMENT";
case FDRState::Token::BUFFER_EXTENTS:
return "BUFFER_EXTENTS";
case FDRState::Token::PID_RECORD:
return "PID_RECORD";
}
return "UNKNOWN";
}
@ -268,6 +278,23 @@ Error processFDRWallTimeRecord(FDRState &State, uint8_t RecordFirstByte,
return Error::success();
}
/// State transition when a PidRecord is encountered.
Error processFDRPidRecord(FDRState &State, uint8_t RecordFirstByte,
DataExtractor &RecordExtractor) {
if (State.Expects != FDRState::Token::PID_RECORD)
return make_error<StringError>(
Twine("Malformed log. Read Pid record kind out of sequence; "
"expected: ") +
fdrStateToTwine(State.Expects),
std::make_error_code(std::errc::executable_format_error));
uint32_t OffsetPtr = 1; // Read starting after the first byte.
State.ProcessId = RecordExtractor.getU32(&OffsetPtr);
State.Expects = FDRState::Token::NEW_CPU_ID_RECORD;
return Error::success();
}
/// State transition when a CustomEventMarker is encountered.
Error processCustomEventMarker(FDRState &State, uint8_t RecordFirstByte,
DataExtractor &RecordExtractor,
@ -325,6 +352,9 @@ Error processFDRCallArgumentRecord(FDRState &State, uint8_t RecordFirstByte,
/// Beginning with Version 2 of the FDR log, we do not depend on the size of the
/// buffer, but rather use the extents to determine how far to read in the log
/// for this particular buffer.
///
/// In Version 3, FDR log now includes a pid metadata record after
/// WallTimeMarker
Error processFDRMetadataRecord(FDRState &State, uint8_t RecordFirstByte,
DataExtractor &RecordExtractor,
size_t &RecordSize,
@ -361,6 +391,9 @@ Error processFDRMetadataRecord(FDRState &State, uint8_t RecordFirstByte,
if (auto E =
processFDRWallTimeRecord(State, RecordFirstByte, RecordExtractor))
return E;
// In Version 3 and and above, a PidRecord is expected after WallTimeRecord
if (Version >= 3)
State.Expects = FDRState::Token::PID_RECORD;
break;
case 5: // CustomEventMarker
if (auto E = processCustomEventMarker(State, RecordFirstByte,
@ -376,6 +409,10 @@ Error processFDRMetadataRecord(FDRState &State, uint8_t RecordFirstByte,
if (auto E = processBufferExtents(State, RecordFirstByte, RecordExtractor))
return E;
break;
case 9: // Pid
if (auto E = processFDRPidRecord(State, RecordFirstByte, RecordExtractor))
return E;
break;
default:
// Widen the record type to uint16_t to prevent conversion to char.
return make_error<StringError>(
@ -405,6 +442,10 @@ Error processFDRFunctionRecord(FDRState &State, uint8_t RecordFirstByte,
return make_error<StringError>(
"Malformed log. Received Function Record when expecting wallclock.",
std::make_error_code(std::errc::executable_format_error));
case FDRState::Token::PID_RECORD:
return make_error<StringError>(
"Malformed log. Received Function Record when expecting pid.",
std::make_error_code(std::errc::executable_format_error));
case FDRState::Token::NEW_CPU_ID_RECORD:
return make_error<StringError>(
"Malformed log. Received Function Record before first CPU record.",
@ -434,6 +475,7 @@ Error processFDRFunctionRecord(FDRState &State, uint8_t RecordFirstByte,
}
Record.CPU = State.CPUId;
Record.TId = State.ThreadId;
Record.PId = State.ProcessId;
// Back up to read first 32 bits, including the 4 we pulled RecordType
// and RecordKind out of. The remaining 28 are FunctionId.
uint32_t OffsetPtr = 0;
@ -477,6 +519,7 @@ Error processFDRFunctionRecord(FDRState &State, uint8_t RecordFirstByte,
/// BufSize: 8 byte unsigned integer indicating how large the buffer is.
/// NewBuffer: 16 byte metadata record with Thread Id.
/// WallClockTime: 16 byte metadata record with human readable time.
/// Pid: 16 byte metadata record with Pid
/// NewCPUId: 16 byte metadata record with CPUId and a 64 bit TSC reading.
/// EOB: 16 byte record in a thread buffer plus mem garbage to fill BufSize.
/// FunctionSequence: NewCPUId | TSCWrap | FunctionRecord
@ -490,6 +533,11 @@ Error processFDRFunctionRecord(FDRState &State, uint8_t RecordFirstByte,
/// BufferExtents: 16 byte metdata record describing how many usable bytes are
/// in the buffer. This is measured from the start of the buffer
/// and must always be at least 48 (bytes).
///
/// In Version 3, we make the following changes:
///
/// ThreadBuffer: BufferExtents NewBuffer WallClockTime Pid NewCPUId
/// FunctionSequence
/// EOB: *deprecated*
Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader,
std::vector<XRayRecord> &Records) {
@ -523,6 +571,7 @@ Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader,
InitialExpectation = FDRState::Token::NEW_BUFFER_RECORD_OR_EOF;
break;
case 2:
case 3:
InitialExpectation = FDRState::Token::BUFFER_EXTENTS;
break;
default:
@ -530,7 +579,7 @@ Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader,
Twine("Unsupported version '") + Twine(FileHeader.Version) + "'",
std::make_error_code(std::errc::executable_format_error));
}
FDRState State{0, 0, 0, InitialExpectation, BufferSize, 0};
FDRState State{0, 0, 0, 0, InitialExpectation, BufferSize, 0};
// RecordSize will tell the loop how far to seek ahead based on the record
// type that we have just read.
@ -572,7 +621,7 @@ Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader,
if (!isBufferExtents)
State.CurrentBufferConsumed += RecordSize;
assert(State.CurrentBufferConsumed <= State.CurrentBufferSize);
if (FileHeader.Version == 2 &&
if ((FileHeader.Version == 2 || FileHeader.Version == 3) &&
State.CurrentBufferSize == State.CurrentBufferConsumed) {
// In Version 2 of the log, we don't need to scan to the end of the thread
// buffer if we've already consumed all the bytes we need to.
@ -621,8 +670,8 @@ Error loadYAMLLog(StringRef Data, XRayFileHeader &FileHeader,
Records.clear();
std::transform(Trace.Records.begin(), Trace.Records.end(),
std::back_inserter(Records), [&](const YAMLXRayRecord &R) {
return XRayRecord{R.RecordType, R.CPU, R.Type, R.FuncId,
R.TSC, R.TId, R.CallArgs};
return XRayRecord{R.RecordType, R.CPU, R.Type, R.FuncId,
R.TSC, R.TId, R.PId, R.CallArgs};
});
return Error::success();
}
@ -681,7 +730,7 @@ Expected<Trace> llvm::xray::loadTraceFile(StringRef Filename, bool Sort) {
Trace T;
switch (Type) {
case NAIVE_FORMAT:
if (Version == 1 || Version == 2) {
if (Version == 1 || Version == 2 || Version == 3) {
if (auto E = loadNaiveFormatLog(Data, T.FileHeader, T.Records))
return std::move(E);
} else {
@ -692,7 +741,7 @@ Expected<Trace> llvm::xray::loadTraceFile(StringRef Filename, bool Sort) {
}
break;
case FLIGHT_DATA_RECORDER_FORMAT:
if (Version == 1 || Version == 2) {
if (Version == 1 || Version == 2 || Version == 3) {
if (auto E = loadFDRLog(Data, T.FileHeader, T.Records))
return std::move(E);
} else {

Binary file not shown.

View File

@ -0,0 +1,17 @@
; RUN: llvm-xray convert %S/Inputs/basic-log-arg1-version-3.xray -f=yaml -o - | FileCheck %s
; CHECK: ---
; CHECK-NEXT: header:
; CHECK-NEXT: version: 3
; CHECK-NEXT: type: 0
; CHECK-NEXT: constant-tsc: true
; CHECK-NEXT: nonstop-tsc: true
; CHECK-NEXT: cycle-frequency: 3900000000
; CHECK-NEXT: records:
; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 0, thread: 2590, process: 2590, kind: function-enter, tsc: 2033303630902004 }
; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033403115246844 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 0, thread: 2590, process: 2590, kind: function-enter, tsc: 2033490200702516 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033504122687120 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', args: [ 67 ], cpu: 0, thread: 2590, process: 2590, kind: function-enter-arg, tsc: 2033505343905936 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033505343936752 }
; CHECK-NEXT: ...

View File

@ -0,0 +1,19 @@
; RUN: llvm-xray convert %S/Inputs/basic-log-version-3.xray -f=yaml -o - | FileCheck %s
; CHECK: ---
; CHECK-NEXT: header:
; CHECK-NEXT: version: 3
; CHECK-NEXT: type: 0
; CHECK-NEXT: constant-tsc: true
; CHECK-NEXT: nonstop-tsc: true
; CHECK-NEXT: cycle-frequency: 3900000000
; CHECK-NEXT: records:
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070767347414784 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070767347496472 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070768324320264 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070768324344100 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070768921602152 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070768921625968 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070769627174140 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070769627197624 }
; CHECK-NEXT: ...

View File

@ -0,0 +1,17 @@
; RUN: llvm-xray convert %S/Inputs/fdr-log-arg1-version-3.xray -f=yaml -o - | FileCheck %s
; CHECK: ---
; CHECK-NEXT: header:
; CHECK-NEXT: version: 3
; CHECK-NEXT: type: 1
; CHECK-NEXT: constant-tsc: true
; CHECK-NEXT: nonstop-tsc: true
; CHECK-NEXT: cycle-frequency: 3900000000
; CHECK-NEXT: records:
; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 6, thread: 2631, process: 2631, kind: function-enter, tsc: 2034042117104344 }
; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034042117199088 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 6, thread: 2631, process: 2631, kind: function-enter, tsc: 2034043145686378 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034043145762200 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', args: [ 67 ], cpu: 6, thread: 2631, process: 2631, kind: function-enter-arg, tsc: 2034049739853430 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034049739878154 }
; CHECK-NEXT: ...

View File

@ -0,0 +1,17 @@
; RUN: llvm-xray convert %S/Inputs/fdr-log-version-3.xray -f=yaml -o - | FileCheck %s
; CHECK: ---
; CHECK-NEXT: header:
; CHECK-NEXT: version: 3
; CHECK-NEXT: type: 1
; CHECK-NEXT: constant-tsc: true
; CHECK-NEXT: nonstop-tsc: true
; CHECK-NEXT: cycle-frequency: 3900000000
; CHECK-NEXT: records:
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069294857657498 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069294857707502 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069295590705912 }
; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069295590734308 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069296377598128 }
; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069296377627032 }
; CHECK-NEXT: ...

View File

@ -476,9 +476,9 @@ static CommandRegistration Unused(&Account, []() -> Error {
errs()
<< "Error processing record: "
<< llvm::formatv(
R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}}})",
R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}; process-id: {6}}})",
Record.RecordType, Record.CPU, Record.Type, Record.FuncId,
Record.TId)
Record.TSC, Record.TId, Record.PId)
<< '\n';
for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) {
errs() << "Thread ID: " << ThreadStack.first << "\n";

View File

@ -91,7 +91,7 @@ void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) {
Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId,
Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId)
: llvm::to_string(R.FuncId),
R.TSC, R.TId, R.CallArgs});
R.TSC, R.TId, R.PId, R.CallArgs});
}
Output Out(OS, nullptr, 0);
Out << Trace;
@ -141,7 +141,12 @@ void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) {
Writer.write(R.FuncId);
Writer.write(R.TSC);
Writer.write(R.TId);
Writer.write(Padding4B);
if (FH.Version >= 3)
Writer.write(R.PId);
else
Writer.write(Padding4B);
Writer.write(Padding4B);
Writer.write(Padding4B);
}
@ -229,19 +234,29 @@ StackTrieNode *findOrCreateStackNode(
return CurrentStack;
}
void writeTraceViewerRecord(raw_ostream &OS, int32_t FuncId, uint32_t TId,
bool Symbolize,
void writeTraceViewerRecord(uint16_t Version, raw_ostream &OS, int32_t FuncId,
uint32_t TId, uint32_t PId, bool Symbolize,
const FuncIdConversionHelper &FuncIdHelper,
double EventTimestampUs,
const StackTrieNode &StackCursor,
StringRef FunctionPhenotype) {
OS << " ";
OS << llvm::formatv(
R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
R"("ts" : "{3:f3}", "sf" : "{4}" })",
(Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
: llvm::to_string(FuncId)),
FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
if (Version >= 3) {
OS << llvm::formatv(
R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "{3}", )"
R"("ts" : "{4:f4}", "sf" : "{5}" })",
(Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
: llvm::to_string(FuncId)),
FunctionPhenotype, TId, PId, EventTimestampUs,
StackCursor.ExtraData.id);
} else {
OS << llvm::formatv(
R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
R"("ts" : "{3:f3}", "sf" : "{4}" })",
(Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
: llvm::to_string(FuncId)),
FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
}
}
} // namespace
@ -249,6 +264,7 @@ void writeTraceViewerRecord(raw_ostream &OS, int32_t FuncId, uint32_t TId,
void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
raw_ostream &OS) {
const auto &FH = Records.getFileHeader();
auto Version = FH.Version;
auto CycleFreq = FH.CycleFrequency;
unsigned id_counter = 0;
@ -282,11 +298,11 @@ void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
StackRootsByThreadId, StacksByStackId,
&id_counter, NodeStore);
// Each record is represented as a json dictionary with function name,
// type of B for begin or E for end, thread id, process id (faked),
// type of B for begin or E for end, thread id, process id,
// timestamp in microseconds, and a stack frame id. The ids are logged
// in an id dictionary after the events.
writeTraceViewerRecord(OS, R.FuncId, R.TId, Symbolize, FuncIdHelper,
EventTimestampUs, *StackCursor, "B");
writeTraceViewerRecord(Version, OS, R.FuncId, R.TId, R.PId, Symbolize,
FuncIdHelper, EventTimestampUs, *StackCursor, "B");
break;
case RecordTypes::EXIT:
case RecordTypes::TAIL_EXIT:
@ -297,9 +313,9 @@ void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
// (And/Or in loop termination below)
StackTrieNode *PreviousCursor = nullptr;
do {
writeTraceViewerRecord(OS, StackCursor->FuncId, R.TId, Symbolize,
FuncIdHelper, EventTimestampUs, *StackCursor,
"E");
writeTraceViewerRecord(Version, OS, StackCursor->FuncId, R.TId, R.PId,
Symbolize, FuncIdHelper, EventTimestampUs,
*StackCursor, "E");
PreviousCursor = StackCursor;
StackCursor = StackCursor->Parent;
} while (PreviousCursor->FuncId != R.FuncId && StackCursor != nullptr);