// pro/epi tracker(Dwarf CFI) for the precise check.
uint64_t Source = State.getCurrentLBRSource();
auto *ParentFrame = State.getParentFrame();
+
if (ParentFrame == State.getDummyRootPtr() ||
ParentFrame->Address != Source) {
State.switchToFrame(Source);
State.InstPtr.update(LBR.Source);
}
-void VirtualUnwinder::unwindBranchWithinFrame(UnwindState &State) {
+void VirtualUnwinder::unwindBranch(UnwindState &State) {
// TODO: Tolerate tail call for now, as we may see tail call from libraries.
// This is only for intra function branches, excluding tail calls.
uint64_t Source = State.getCurrentLBRSource();
void VirtualUnwinder::recordBranchCount(const LBREntry &Branch,
UnwindState &State, uint64_t Repeat) {
- if (Branch.IsArtificial)
+ if (Branch.IsArtificial || Branch.Target == ExternalAddr)
return;
if (Binary->usePseudoProbes()) {
if (!State.validateInitialState())
return false;
- // Also do not attempt linear unwind for the leaf range as it's incomplete.
- bool IsLeaf = true;
-
// Now process the LBR samples in parrallel with stack sample
// Note that we do not reverse the LBR entry order so we can
// unwind the sample stack as we walk through LBR entries.
while (State.hasNextLBR()) {
State.checkStateConsistency();
- // Unwind implicit calls/returns from inlining, along the linear path,
- // break into smaller sub section each with its own calling context.
- if (!IsLeaf) {
+ // Do not attempt linear unwind for the leaf range as it's incomplete.
+ if (!State.IsLastLBR()) {
+ // Unwind implicit calls/returns from inlining, along the linear path,
+ // break into smaller sub section each with its own calling context.
unwindLinear(State, Repeat);
}
- IsLeaf = false;
// Save the LBR branch before it gets unwound.
const LBREntry &Branch = State.getCurrentLBR();
// Unwind returns - check whether the IP is indeed at a return instruction
unwindReturn(State);
} else {
- // Unwind branches - for regular intra function branches, we only
- // need to record branch with context.
- unwindBranchWithinFrame(State);
+ // Unwind branches
+ // For regular intra function branches, we only need to record branch with
+ // context. For an artificial branch cross function boundaries, we got an
+ // issue with returning to external code. Take the two LBR enties for
+ // example: [foo:8(RETURN), ext:1] [ext:3(CALL), bar:1] After perf reader,
+ // we only get[foo:8(RETURN), bar:1], unwinder will be confused like foo
+ // return to bar. Here we detect and treat this case as BRANCH instead of
+ // RETURN which only update the source address.
+ unwindBranch(State);
}
State.advanceLBR();
// Record `branch` with calling context after unwinding.
if (Binary->useFSDiscriminator())
exitWithError("FS discriminator is not supported in CS profile.");
std::set<uint64_t> AllUntrackedCallsites;
+ VirtualUnwinder Unwinder(&SampleCounters, Binary);
for (const auto &Item : AggregatedSamples) {
const PerfSample *Sample = Item.first.getPtr();
- VirtualUnwinder Unwinder(&SampleCounters, Binary);
Unwinder.unwind(Sample, Item.second);
auto &CurrUntrackedCallsites = Unwinder.getUntrackedCallsites();
AllUntrackedCallsites.insert(CurrUntrackedCallsites.begin(),
bool IsOutgoing = SrcIsInternal && !DstIsInternal;
bool IsArtificial = false;
- // Ignore branches outside the current binary. Ignore all remaining branches
- // if there's no incoming branch before the external branch in reverse
- // order.
+ // Ignore branches outside the current binary.
if (IsExternal) {
- if (PrevTrDst)
- continue;
- if (!LBRStack.empty()) {
+ if (!PrevTrDst && !LBRStack.empty()) {
WithColor::warning()
<< "Invalid transfer to external code in LBR record at line "
<< TraceIt.getLineNumber() << ": " << TraceIt.getCurrentLine()
<< "\n";
}
- break;
+ // Do not ignore the entire samples, the remaining LBR can still be
+ // unwound using a context-less stack.
+ continue;
}
if (IsOutgoing) {
if (!PrevTrDst) {
- // This is unpaired outgoing jump which is likely due to interrupt or
- // incomplete LBR trace. Ignore current and subsequent entries since
- // they are likely in different contexts.
+ // This is a leading outgoing LBR, we should keep processing the LBRs.
+ if (LBRStack.empty()) {
+ NumLeadingOutgoingLBR++;
+ // Record this LBR since current source and next LBR' target is still
+ // a valid range.
+ LBRStack.emplace_back(LBREntry(Src, ExternalAddr, false));
+ continue;
+ }
+ // This is middle unpaired outgoing jump which is likely due to
+ // interrupt or incomplete LBR trace. Ignore current and subsequent
+ // entries since they are likely in different contexts.
break;
}
}
TraceIt.advance();
// Currently intermixed frame from different binaries is not supported.
- // Ignore caller frames not from binary of interest.
- if (!Binary->addressIsCode(FrameAddr))
- break;
+ if (!Binary->addressIsCode(FrameAddr)) {
+ if (CallStack.empty())
+ NumLeafExternalFrame++;
+ // Push a special value(ExternalAddr) for the external frames so that
+ // unwinder can still work on this with artificial Call/Return branch.
+ // After unwinding, the context will be truncated for external frame.
+ // Also deduplicate the consecutive external addresses.
+ if (CallStack.empty() || CallStack.back() != ExternalAddr)
+ CallStack.emplace_back(ExternalAddr);
+ continue;
+ }
// We need to translate return address to call address for non-leaf frames.
if (!CallStack.empty()) {
CallStack.emplace_back(FrameAddr);
}
+ // Strip out the bottom external addr.
+ if (CallStack.size() > 1 && CallStack.back() == ExternalAddr)
+ CallStack.pop_back();
+
// Skip other unrelated line, find the next valid LBR line
// Note that even for empty call stack, we should skip the address at the
// bottom, otherwise the following pass may generate a truncated callstack
}
void PerfScriptReader::parseSample(TraceStream &TraceIt) {
+ NumTotalSample++;
uint64_t Count = parseAggregatedCount(TraceIt);
assert(Count >= 1 && "Aggregated count should be >= 1!");
parseSample(TraceIt, Count);
// Parse perf traces and do aggregation.
parseAndAggregateTrace();
+ emitWarningSummary(NumLeafExternalFrame, NumTotalSample,
+ "of samples have leaf external frame in call stack.");
+ emitWarningSummary(NumLeadingOutgoingLBR, NumTotalSample,
+ "of samples have leading external LBR.");
+
// Generate unsymbolized profile.
warnTruncatedStack();
warnInvalidRange();
Hashable<PerfSample>::Hash, Hashable<PerfSample>::Equal>;
using SampleVector = SmallVector<std::tuple<uint64_t, uint64_t, uint64_t>, 16>;
+
+// The special frame addresses.
+enum SpecialFrameAddr {
+ // Dummy root of frame trie.
+ DummyRoot = 0,
+ // Represent all the addresses outside of current binary.
+ ExternalAddr = 1,
+};
+
// The state for the unwinder, it doesn't hold the data but only keep the
// pointer/index of the data, While unwinding, the CallStack is changed
// dynamicially and will be recorded as the context of the sample
const ProfiledBinary *Binary;
// Call stack trie node
struct ProfiledFrame {
- const uint64_t Address = 0;
+ const uint64_t Address = DummyRoot;
ProfiledFrame *Parent;
SampleVector RangeSamples;
SampleVector BranchSamples;
void recordBranchCount(uint64_t Source, uint64_t Target, uint64_t Count) {
BranchSamples.emplace_back(std::make_tuple(Source, Target, Count));
}
- bool isDummyRoot() { return Address == 0; }
+ bool isDummyRoot() { return Address == DummyRoot; }
+ bool isExternalFrame() { return Address == ExternalAddr; }
bool isLeafFrame() { return Children.empty(); }
};
bool validateInitialState() {
uint64_t LBRLeaf = LBRStack[LBRIndex].Target;
uint64_t LeafAddr = CurrentLeafFrame->Address;
+ assert((LBRLeaf != ExternalAddr || LBRLeaf == LeafAddr) &&
+ "External leading LBR should match the leaf frame.");
+
// When we take a stack sample, ideally the sampling distance between the
// leaf IP of stack and the last LBR target shouldn't be very large.
// Use a heuristic size (0x100) to filter out broken records.
uint64_t getCurrentLBRSource() const { return LBRStack[LBRIndex].Source; }
uint64_t getCurrentLBRTarget() const { return LBRStack[LBRIndex].Target; }
const LBREntry &getCurrentLBR() const { return LBRStack[LBRIndex]; }
+ bool IsLastLBR() const { return LBRIndex == 0; }
+ bool getLBRStackSize() const { return LBRStack.size(); }
void advanceLBR() { LBRIndex++; }
-
ProfiledFrame *getParentFrame() { return CurrentLeafFrame->Parent; }
void pushFrame(uint64_t Address) {
ProfiledBinary *Binary;
FrameStack(ProfiledBinary *B) : Binary(B) {}
bool pushFrame(UnwindState::ProfiledFrame *Cur) {
+ // Truncate the context for external frame since this isn't a real call
+ // context the compiler will see
+ if (Cur->isExternalFrame())
+ return false;
Stack.push_back(Cur->Address);
return true;
}
ProfiledBinary *Binary;
ProbeStack(ProfiledBinary *B) : Binary(B) {}
bool pushFrame(UnwindState::ProfiledFrame *Cur) {
+ // Truncate the context for external frame since this isn't a real call
+ // context the compiler will see
+ if (Cur->isExternalFrame())
+ return false;
const MCDecodedPseudoProbe *CallProbe =
Binary->getCallProbeForAddr(Cur->Address);
// We may not find a probe for a merged or external callsite.
void unwindCall(UnwindState &State);
void unwindLinear(UnwindState &State, uint64_t Repeat);
void unwindReturn(UnwindState &State);
- void unwindBranchWithinFrame(UnwindState &State);
+ void unwindBranch(UnwindState &State);
template <typename T>
void collectSamplesFromFrame(UnwindState::ProfiledFrame *Cur, T &Stack);
ContextSampleCounterMap SampleCounters;
bool ProfileIsCSFlat = false;
+
+ uint64_t NumTotalSample = 0;
+ uint64_t NumLeafExternalFrame = 0;
+ uint64_t NumLeadingOutgoingLBR = 0;
};
// Read perf script to parse the events and samples.