Skip to content

Commit 240a0d1

Browse files
authored
[llvm-profgen] Loading binary functions from .symtab when DWARF info is incomplete (#163654)
Indexing in .debug_str section could lead to integer overflow when in DWARF32 format. https://github.com/llvm/llvm-project/blob/e61e6251b692ffe71910bad22b82e41313f003cf/llvm/lib/DWP/DWP.cpp#L35C30-L35C47 This can lead to missing symbols from the DWARF info, and hurts profile quality. As a workaround, we may use information from the symbol table (.symtab), and recover the missing symbols with addresses and ranges. Output: ``` # Before ... warning: 6.64%(338916009/5106344252) of samples are from ranges that do not belong to any functions. # After ... warning: 0.07%(3501587/4906133148) of samples are from ranges that do not belong to any functions. warning: 5.71%(280266919/4906133148) of samples are from ranges that belong to functions recovered from symbol table. ``` We see 0.4% - 1.35% performance improvements on our internal services where profiles are generated with binaries that have .debug_str section over 4GB.
1 parent 278d28f commit 240a0d1

File tree

9 files changed

+266
-8
lines changed

9 files changed

+266
-8
lines changed

llvm/include/llvm/MC/MCPseudoProbe.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ class MCDecodedPseudoProbeInlineTree
328328

329329
// Return false if it's a dummy inline site
330330
bool hasInlineSite() const { return !isRoot() && !Parent->isRoot(); }
331+
bool isTopLevelFunc() const { return !isRoot() && Parent->isRoot(); }
331332
InlineSite getInlineSite() const { return InlineSite(Guid, ProbeId); }
332333
void setProbes(MutableArrayRef<MCDecodedPseudoProbe> ProbesRef) {
333334
Probes = ProbesRef.data();

llvm/include/llvm/ProfileData/SampleProf.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,13 +1214,19 @@ class FunctionSamples {
12141214
// Note the sequence of the suffixes in the knownSuffixes array matters.
12151215
// If suffix "A" is appended after the suffix "B", "A" should be in front
12161216
// of "B" in knownSuffixes.
1217-
const char *KnownSuffixes[] = {LLVMSuffix, PartSuffix, UniqSuffix};
1217+
const SmallVector<StringRef> KnownSuffixes{LLVMSuffix, PartSuffix,
1218+
UniqSuffix};
1219+
return getCanonicalFnName(FnName, KnownSuffixes, Attr);
1220+
}
1221+
1222+
static StringRef getCanonicalFnName(StringRef FnName,
1223+
ArrayRef<StringRef> Suffixes,
1224+
StringRef Attr = "selected") {
12181225
if (Attr == "" || Attr == "all")
12191226
return FnName.split('.').first;
12201227
if (Attr == "selected") {
12211228
StringRef Cand(FnName);
1222-
for (const auto &Suf : KnownSuffixes) {
1223-
StringRef Suffix(Suf);
1229+
for (const auto Suffix : Suffixes) {
12241230
// If the profile contains ".__uniq." suffix, don't strip the
12251231
// suffix for names in the IR.
12261232
if (Suffix == UniqSuffix && FunctionSamples::HasUniqSuffix)
@@ -1229,7 +1235,7 @@ class FunctionSamples {
12291235
if (It == StringRef::npos)
12301236
continue;
12311237
auto Dit = Cand.rfind('.');
1232-
if (Dit == It + Suffix.size() - 1)
1238+
if (Dit == It || Dit == It + Suffix.size() - 1)
12331239
Cand = Cand.substr(0, It);
12341240
}
12351241
return Cand;
18.3 KB
Binary file not shown.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
; RUN: rm -rf %t
2+
; RUN: mkdir -p %t
3+
; RUN: cd %t
4+
5+
; RUN: echo -e "1\n401120-40113b:1\n1\n40112f->401110:1" > %t.prof
6+
7+
; Test --load-function-from-symbol=0
8+
; RUN: llvm-profgen --format=text --unsymbolized-profile=%t.prof --binary=%S/Inputs/missing-dwarf.exe --output=%t1 --fill-zero-for-all-funcs --show-detailed-warning --use-offset=0 --load-function-from-symbol=0 2>&1 | FileCheck %s --check-prefix=CHECK-NO-LOAD-SYMTAB
9+
10+
; CHECK-NO-LOAD-SYMTAB: warning: Loading of DWARF info completed, but no binary functions have been retrieved.
11+
12+
; Test --load-function-from-symbol=1
13+
; RUN: llvm-profgen --format=text --unsymbolized-profile=%t.prof --binary=%S/Inputs/missing-dwarf.exe --output=%t2 --fill-zero-for-all-funcs --show-detailed-warning --use-offset=0 --load-function-from-symbol=1
14+
; RUN: FileCheck %s --input-file %t2 --check-prefix=CHECK-LOAD-SYMTAB
15+
16+
; CHECK-LOAD-SYMTAB: main:2:1
17+
; CHECK-LOAD-SYMTAB-NEXT: 1: 1
18+
; CHECK-LOAD-SYMTAB-NEXT: 2: 1 foo:1
19+
; CHECK-LOAD-SYMTAB-NEXT: !CFGChecksum: 281479271677951
20+
; CHECK-LOAD-SYMTAB-NEXT: foo:0:0
21+
; CHECK-LOAD-SYMTAB-NEXT: 1: 0
22+
; CHECK-LOAD-SYMTAB-NEXT: !CFGChecksum: 4294967295
23+
24+
; Build instructions:
25+
; missing-dwarf.o: clang -gsplit-dwarf=split -fdebug-compilation-dir=. test.c -fdebug-info-for-profiling -fpseudo-probe-for-profiling -O0 -g -o missing-dwarf.o -c
26+
; missing-dwarf.exe: clang -fdebug-compilation-dir=. missing-dwarf.o -o missing-dwarf.exe -fdebug-info-for-profiling -fpseudo-probe-for-profiling -O0 -g
27+
28+
; Source code:
29+
30+
int foo() {
31+
return 1;
32+
}
33+
34+
int main() {
35+
foo();
36+
return 0;
37+
}

llvm/tools/llvm-profgen/Options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern cl::opt<bool> ShowDetailedWarning;
2222
extern cl::opt<bool> InferMissingFrames;
2323
extern cl::opt<bool> EnableCSPreInliner;
2424
extern cl::opt<bool> UseContextCostForPreInliner;
25+
extern cl::opt<bool> LoadFunctionFromSymbol;
2526

2627
} // end namespace llvm
2728

llvm/tools/llvm-profgen/PerfReader.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,7 @@ void PerfScriptReader::warnInvalidRange() {
12841284
uint64_t TotalRangeNum = 0;
12851285
uint64_t InstNotBoundary = 0;
12861286
uint64_t UnmatchedRange = 0;
1287+
uint64_t RecoveredRange = 0;
12871288
uint64_t RangeCrossFunc = 0;
12881289
uint64_t BogusRange = 0;
12891290

@@ -1309,6 +1310,9 @@ void PerfScriptReader::warnInvalidRange() {
13091310
continue;
13101311
}
13111312

1313+
if (FRange->Func->NameStatus != DwarfNameStatus::Matched)
1314+
RecoveredRange += I.second;
1315+
13121316
if (EndAddress >= FRange->EndAddress) {
13131317
RangeCrossFunc += I.second;
13141318
WarnInvalidRange(StartAddress, EndAddress, RangeCrossFuncMsg);
@@ -1328,6 +1332,9 @@ void PerfScriptReader::warnInvalidRange() {
13281332
emitWarningSummary(
13291333
UnmatchedRange, TotalRangeNum,
13301334
"of samples are from ranges that do not belong to any functions.");
1335+
emitWarningSummary(RecoveredRange, TotalRangeNum,
1336+
"of samples are from ranges that belong to functions "
1337+
"recovered from symbol table.");
13311338
emitWarningSummary(
13321339
RangeCrossFunc, TotalRangeNum,
13331340
"of samples are from ranges that do cross function boundaries.");

llvm/tools/llvm-profgen/ProfileGenerator.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,11 @@ ProfileGenerator::getTopLevelFunctionProfile(FunctionId FuncName) {
503503
void ProfileGenerator::generateProfile() {
504504
collectProfiledFunctions();
505505

506-
if (Binary->usePseudoProbes())
506+
if (Binary->usePseudoProbes()) {
507507
Binary->decodePseudoProbe();
508+
if (LoadFunctionFromSymbol)
509+
Binary->loadSymbolsFromPseudoProbe();
510+
}
508511

509512
if (SampleCounters) {
510513
if (Binary->usePseudoProbes()) {
@@ -732,6 +735,14 @@ ProfileGeneratorBase::getCalleeNameForAddress(uint64_t TargetAddress) {
732735
if (!FRange || !FRange->IsFuncEntry)
733736
return StringRef();
734737

738+
// DWARF and symbol table may have mismatching function names. Instead, we'll
739+
// try to use its pseudo probe name first.
740+
if (Binary->usePseudoProbes()) {
741+
auto FuncName = Binary->findPseudoProbeName(FRange->Func);
742+
if (FuncName.size())
743+
return FunctionSamples::getCanonicalFnName(FuncName);
744+
}
745+
735746
return FunctionSamples::getCanonicalFnName(FRange->getFuncName());
736747
}
737748

@@ -919,6 +930,8 @@ void CSProfileGenerator::generateProfile() {
919930
Binary->decodePseudoProbe();
920931
if (InferMissingFrames)
921932
initializeMissingFrameInferrer();
933+
if (LoadFunctionFromSymbol)
934+
Binary->loadSymbolsFromPseudoProbe();
922935
}
923936

924937
if (SampleCounters) {

llvm/tools/llvm-profgen/ProfiledBinary.cpp

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ cl::opt<bool> ShowSourceLocations("show-source-locations",
3737
cl::desc("Print source locations."),
3838
cl::cat(ProfGenCategory));
3939

40+
cl::opt<bool> LoadFunctionFromSymbol(
41+
"load-function-from-symbol", cl::init(true),
42+
cl::desc(
43+
"Gather additional binary function info from symbols (e.g. .symtab) in "
44+
"case dwarf info is incomplete. Only support binaries in ELF format "
45+
"with pseudo probe, for other formats, this flag will be a no-op."),
46+
cl::cat(ProfGenCategory));
47+
4048
static cl::opt<bool>
4149
ShowCanonicalFnName("show-canonical-fname",
4250
cl::desc("Print canonical function name."),
@@ -257,6 +265,9 @@ void ProfiledBinary::load() {
257265
if (ShowDisassemblyOnly)
258266
decodePseudoProbe(Obj);
259267

268+
if (LoadFunctionFromSymbol && UsePseudoProbes)
269+
loadSymbolsFromSymtab(Obj);
270+
260271
// Disassemble the text sections.
261272
disassemble(Obj);
262273

@@ -461,6 +472,13 @@ void ProfiledBinary::decodePseudoProbe(const ObjectFile *Obj) {
461472
} else {
462473
for (auto *F : ProfiledFunctions) {
463474
GuidFilter.insert(Function::getGUIDAssumingExternalLinkage(F->FuncName));
475+
// DWARF name might be broken when a DWARF32 .debug_str.dwo section
476+
// execeeds 4GB. We expect symbol table to contain the correct function
477+
// names which matches the pseudo probe. Adding back all the GUIDs if
478+
// possible.
479+
auto AltGUIDs = AlternativeFunctionGUIDs.equal_range(F);
480+
for (const auto &[_, Func] : make_range(AltGUIDs))
481+
GuidFilter.insert(Func);
464482
for (auto &Range : F->Ranges) {
465483
auto GUIDs = StartAddrToSymMap.equal_range(Range.first);
466484
for (const auto &[StartAddr, Func] : make_range(GUIDs))
@@ -522,7 +540,9 @@ void ProfiledBinary::setIsFuncEntry(FuncRange *FuncRange,
522540
// Set IsFuncEntry to ture if there is only one range in the function or the
523541
// RangeSymName from ELF is equal to its DWARF-based function name.
524542
if (FuncRange->Func->Ranges.size() == 1 ||
525-
(!FuncRange->IsFuncEntry && FuncRange->getFuncName() == RangeSymName))
543+
(!FuncRange->IsFuncEntry &&
544+
(FuncRange->getFuncName() == RangeSymName ||
545+
FuncRange->Func->NameStatus != DwarfNameStatus::Matched)))
526546
FuncRange->IsFuncEntry = true;
527547
}
528548

@@ -604,13 +624,13 @@ bool ProfiledBinary::dissassembleSymbol(std::size_t SI, ArrayRef<uint8_t> Bytes,
604624
// Record potential call targets for tail frame inference later-on.
605625
if (InferMissingFrames && FRange) {
606626
uint64_t Target = 0;
607-
MIA->evaluateBranch(Inst, Address, Size, Target);
627+
bool Err = MIA->evaluateBranch(Inst, Address, Size, Target);
608628
if (MCDesc.isCall()) {
609629
// Indirect call targets are unknown at this point. Recording the
610630
// unknown target (zero) for further LBR-based refinement.
611631
MissingContextInferrer->CallEdges[Address].insert(Target);
612632
} else if (MCDesc.isUnconditionalBranch()) {
613-
assert(Target &&
633+
assert(Err &&
614634
"target should be known for unconditional direct branch");
615635
// Any inter-function unconditional jump is considered tail call at
616636
// this point. This is not 100% accurate and could further be
@@ -820,6 +840,100 @@ void ProfiledBinary::populateSymbolAddressList(const ObjectFile *Obj) {
820840
}
821841
}
822842

843+
void ProfiledBinary::loadSymbolsFromSymtab(const ObjectFile *Obj) {
844+
// Load binary functions from symbol table when Debug info is incomplete.
845+
// Strip the internal suffixes which are not reflected in the DWARF info.
846+
const SmallVector<StringRef, 10> Suffixes(
847+
{// Internal suffixes from CoroSplit pass
848+
".cleanup", ".destroy", ".resume",
849+
// Internal suffixes from Bolt
850+
".cold", ".warm",
851+
// Compiler/LTO internal
852+
".llvm.", ".part.", ".isra.", ".constprop.", ".lto_priv."});
853+
StringRef FileName = Obj->getFileName();
854+
// Only apply this to ELF binary. e.g. COFF file format doesn't have `size`
855+
// field in the symbol table.
856+
bool IsELFObject = isa<ELFObjectFileBase>(Obj);
857+
if (!IsELFObject)
858+
return;
859+
for (const SymbolRef &Symbol : Obj->symbols()) {
860+
const SymbolRef::Type Type = unwrapOrError(Symbol.getType(), FileName);
861+
const uint64_t StartAddr = unwrapOrError(Symbol.getAddress(), FileName);
862+
const StringRef Name = unwrapOrError(Symbol.getName(), FileName);
863+
uint64_t Size = 0;
864+
if (LLVM_LIKELY(IsELFObject)) {
865+
ELFSymbolRef ElfSymbol(Symbol);
866+
Size = ElfSymbol.getSize();
867+
}
868+
869+
if (Size == 0 || Type != SymbolRef::ST_Function)
870+
continue;
871+
872+
const uint64_t EndAddr = StartAddr + Size;
873+
const StringRef SymName =
874+
FunctionSamples::getCanonicalFnName(Name, Suffixes);
875+
assert(StartAddr < EndAddr && StartAddr >= getPreferredBaseAddress() &&
876+
"Function range is invalid.");
877+
878+
auto Range = findFuncRange(StartAddr);
879+
if (!Range) {
880+
assert(findFuncRange(EndAddr - 1) == nullptr &&
881+
"Function range overlaps with existing functions.");
882+
// Function from symbol table not found previously in DWARF, store ranges.
883+
auto Ret = BinaryFunctions.emplace(SymName, BinaryFunction());
884+
auto &Func = Ret.first->second;
885+
if (Ret.second) {
886+
Func.FuncName = Ret.first->first;
887+
HashBinaryFunctions[Function::getGUIDAssumingExternalLinkage(SymName)] =
888+
&Func;
889+
}
890+
891+
Func.NameStatus = DwarfNameStatus::Missing;
892+
Func.Ranges.emplace_back(StartAddr, EndAddr);
893+
894+
auto R = StartAddrToFuncRangeMap.emplace(StartAddr, FuncRange());
895+
FuncRange &FRange = R.first->second;
896+
897+
FRange.Func = &Func;
898+
FRange.StartAddress = StartAddr;
899+
FRange.EndAddress = EndAddr;
900+
901+
} else if (SymName != Range->getFuncName()) {
902+
// Function range already found from DWARF, but the symbol name from
903+
// symbol table is inconsistent with debug info. Log this discrepancy and
904+
// the alternative function GUID.
905+
if (ShowDetailedWarning)
906+
WithColor::warning()
907+
<< "Conflicting name for symbol " << Name << " with range ("
908+
<< format("%8" PRIx64, StartAddr) << ", "
909+
<< format("%8" PRIx64, EndAddr) << ")"
910+
<< ", but the DWARF symbol " << Range->getFuncName()
911+
<< " indicates an overlapping range ("
912+
<< format("%8" PRIx64, Range->StartAddress) << ", "
913+
<< format("%8" PRIx64, Range->EndAddress) << ")\n";
914+
915+
assert(StartAddr == Range->StartAddress && EndAddr == Range->EndAddress &&
916+
"Mismatched function range");
917+
918+
Range->Func->NameStatus = DwarfNameStatus::Mismatch;
919+
AlternativeFunctionGUIDs.emplace(
920+
Range->Func, Function::getGUIDAssumingExternalLinkage(SymName));
921+
922+
} else if (StartAddr != Range->StartAddress &&
923+
EndAddr != Range->EndAddress) {
924+
// Function already found in DWARF, but the address range from symbol
925+
// table conflicts/overlaps with the debug info.
926+
WithColor::warning() << "Conflicting range for symbol " << Name
927+
<< " with range (" << format("%8" PRIx64, StartAddr)
928+
<< ", " << format("%8" PRIx64, EndAddr) << ")"
929+
<< ", but the DWARF symbol " << Range->getFuncName()
930+
<< " indicates another range ("
931+
<< format("%8" PRIx64, Range->StartAddress) << ", "
932+
<< format("%8" PRIx64, Range->EndAddress) << ")\n";
933+
}
934+
}
935+
}
936+
823937
void ProfiledBinary::loadSymbolsFromDWARFUnit(DWARFUnit &CompilationUnit) {
824938
for (const auto &DieInfo : CompilationUnit.dies()) {
825939
llvm::DWARFDie Die(&CompilationUnit, &DieInfo);
@@ -1034,6 +1148,58 @@ void ProfiledBinary::computeInlinedContextSizeForFunc(
10341148
}
10351149
}
10361150

1151+
void ProfiledBinary::loadSymbolsFromPseudoProbe() {
1152+
if (!UsePseudoProbes)
1153+
return;
1154+
1155+
const AddressProbesMap &Address2ProbesMap = getAddress2ProbesMap();
1156+
for (auto *Func : ProfiledFunctions) {
1157+
if (Func->NameStatus != DwarfNameStatus::Mismatch)
1158+
continue;
1159+
for (auto &[StartAddr, EndAddr] : Func->Ranges) {
1160+
auto Range = findFuncRangeForStartAddr(StartAddr);
1161+
if (!Range->IsFuncEntry)
1162+
continue;
1163+
const auto &Probe = Address2ProbesMap.find(StartAddr, EndAddr);
1164+
if (Probe.begin() != Probe.end()) {
1165+
const MCDecodedPseudoProbeInlineTree *InlineTreeNode =
1166+
Probe.begin()->get().getInlineTreeNode();
1167+
while (!InlineTreeNode->isTopLevelFunc())
1168+
InlineTreeNode = static_cast<MCDecodedPseudoProbeInlineTree *>(
1169+
InlineTreeNode->Parent);
1170+
1171+
auto TopLevelProbes = InlineTreeNode->getProbes();
1172+
auto TopProbe = TopLevelProbes.begin();
1173+
assert(TopProbe != TopLevelProbes.end() &&
1174+
TopProbe->getAddress() >= StartAddr &&
1175+
TopProbe->getAddress() < EndAddr &&
1176+
"Top level pseudo probe does not match function range");
1177+
1178+
const auto *ProbeDesc = getFuncDescForGUID(InlineTreeNode->Guid);
1179+
auto Ret = PseudoProbeNames.emplace(Func, ProbeDesc->FuncName);
1180+
if (!Ret.second && Ret.first->second != ProbeDesc->FuncName &&
1181+
ShowDetailedWarning)
1182+
WithColor::warning()
1183+
<< "Mismatched pseudo probe names in function " << Func->FuncName
1184+
<< " at range: (" << format("%8" PRIx64, StartAddr) << ", "
1185+
<< format("%8" PRIx64, EndAddr) << "). "
1186+
<< "The previously found pseudo probe name is "
1187+
<< Ret.first->second << " but it conflicts with name "
1188+
<< ProbeDesc->FuncName
1189+
<< " This likely indicates a DWARF error that produces "
1190+
"conflicting symbols at the same starting address.\n";
1191+
}
1192+
}
1193+
}
1194+
}
1195+
1196+
StringRef ProfiledBinary::findPseudoProbeName(const BinaryFunction *Func) {
1197+
auto ProbeName = PseudoProbeNames.find(Func);
1198+
if (ProbeName == PseudoProbeNames.end())
1199+
return StringRef();
1200+
return ProbeName->second;
1201+
}
1202+
10371203
void ProfiledBinary::inferMissingFrames(
10381204
const SmallVectorImpl<uint64_t> &Context,
10391205
SmallVectorImpl<uint64_t> &NewContext) {

0 commit comments

Comments
 (0)