-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[llvm-profgen] Loading binary functions from .symtab when DWARF info is incomplete #163654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 21 commits
f34ab2d
a1f32b5
0fd352d
c097d37
a19064d
e12e694
5eead6b
8f59bfa
a967994
0dc2c66
75dc996
5600e83
0df504e
644fce9
9188eff
b6ae0ba
b1cbdd0
8927a27
108bc08
6e3a5ac
3d8ad53
1344cdd
2978e21
eaab9df
c53e0ae
b707e80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| ; RUN: rm -rf %t | ||
| ; RUN: mkdir -p %t | ||
| ; RUN: cd %t | ||
|
|
||
| ; RUN: echo -e "1\n401120-40113b:1\n1\n40112f->401110:1" > %t.prof | ||
|
|
||
| ; Test --load-function-from-symbol=0 | ||
| ; 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 | ||
|
|
||
| ; CHECK-NO-LOAD-SYMTAB: warning: Loading of DWARF info completed, but no binary functions have been retrieved. | ||
|
|
||
| ; Test --load-function-from-symbol=1 | ||
| ; 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 | ||
| ; RUN: FileCheck %s --input-file %t2 --check-prefix=CHECK-LOAD-SYMTAB | ||
|
|
||
| ; CHECK-LOAD-SYMTAB: main:2:1 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: 1: 1 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: 2: 1 foo:1 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: !CFGChecksum: 281479271677951 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: foo:0:0 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: 1: 0 | ||
| ; CHECK-LOAD-SYMTAB-NEXT: !CFGChecksum: 4294967295 | ||
|
|
||
| ; Build instructions: | ||
| ; 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 | ||
| ; missing-dwarf.exe: clang -fdebug-compilation-dir=. missing-dwarf.o -o missing-dwarf.exe -fdebug-info-for-profiling -fpseudo-probe-for-profiling -O0 -g | ||
|
|
||
| ; Source code: | ||
|
|
||
| int foo() { | ||
| return 1; | ||
| } | ||
|
|
||
| int main() { | ||
| foo(); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -723,7 +723,8 @@ void ProfileGenerator::populateBodySamplesForAllFunctions( | |
| } | ||
|
|
||
| StringRef | ||
| ProfileGeneratorBase::getCalleeNameForAddress(uint64_t TargetAddress) { | ||
| ProfileGeneratorBase::getCalleeNameForAddress(uint64_t TargetAddress, | ||
| bool RestoreSymbolName) { | ||
| // Get the function range by branch target if it's a call branch. | ||
| auto *FRange = Binary->findFuncRangeForStartAddr(TargetAddress); | ||
|
|
||
|
|
@@ -732,6 +733,15 @@ ProfileGeneratorBase::getCalleeNameForAddress(uint64_t TargetAddress) { | |
| if (!FRange || !FRange->IsFuncEntry) | ||
| return StringRef(); | ||
|
|
||
| if (RestoreSymbolName && FRange->Func->HasSymtabName) { | ||
| const AddressProbesMap &Address2ProbesMap = Binary->getAddress2ProbesMap(); | ||
| for (const MCDecodedPseudoProbe &Probe : | ||
| Address2ProbesMap.find(TargetAddress)) { | ||
| if (const auto *ProbeDesc = Binary->getFuncDescForGUID(Probe.getGuid())) | ||
| return FunctionSamples::getCanonicalFnName(ProbeDesc->FuncName); | ||
| } | ||
| } | ||
|
|
||
| return FunctionSamples::getCanonicalFnName(FRange->getFuncName()); | ||
| } | ||
|
|
||
|
|
@@ -1352,7 +1362,7 @@ void CSProfileGenerator::populateBoundarySamplesWithProbes( | |
| getFunctionProfileForLeafProbe(CtxKey, CallProbe); | ||
| FunctionProfile.addBodySamples(CallProbe->getIndex(), 0, Count); | ||
| FunctionProfile.addTotalSamples(Count); | ||
| StringRef CalleeName = getCalleeNameForAddress(TargetAddress); | ||
| StringRef CalleeName = getCalleeNameForAddress(TargetAddress, true); | ||
|
||
| if (CalleeName.size() == 0) | ||
| continue; | ||
| FunctionProfile.addCalledTargetSamples(CallProbe->getIndex(), | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -65,6 +65,13 @@ static cl::list<std::string> DisassembleFunctions( | |||||||||||
| "names only. Only work with show-disassembly-only"), | ||||||||||||
| cl::cat(ProfGenCategory)); | ||||||||||||
|
|
||||||||||||
| static cl::opt<bool> | ||||||||||||
| LoadFunctionFromSymbol("load-function-from-symbol", cl::init(true), | ||||||||||||
|
||||||||||||
| cl::desc("Gather additional binary function info " | ||||||||||||
| "from symbols (e.g. .symtab) in case " | ||||||||||||
| "dwarf info is incomplete."), | ||||||||||||
| cl::cat(ProfGenCategory)); | ||||||||||||
|
|
||||||||||||
| static cl::opt<bool> | ||||||||||||
| KernelBinary("kernel", | ||||||||||||
| cl::desc("Generate the profile for Linux kernel binary."), | ||||||||||||
|
|
@@ -257,6 +264,9 @@ void ProfiledBinary::load() { | |||||||||||
| if (ShowDisassemblyOnly) | ||||||||||||
| decodePseudoProbe(Obj); | ||||||||||||
|
|
||||||||||||
| if (LoadFunctionFromSymbol && UsePseudoProbes) | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is pseudo-probe required here?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh, this is to be compatible with the code here: llvm-project/llvm/tools/llvm-profgen/ProfileGenerator.cpp Lines 510 to 514 in af110e1
UsePseudoProbes is needed so we won't use line numbers to generate profile, which won't work with symtab because symtab only have addresses
|
||||||||||||
| loadSymbolsFromSymtab(Obj); | ||||||||||||
|
|
||||||||||||
| // Disassemble the text sections. | ||||||||||||
| disassemble(Obj); | ||||||||||||
|
|
||||||||||||
|
|
@@ -461,6 +471,11 @@ void ProfiledBinary::decodePseudoProbe(const ObjectFile *Obj) { | |||||||||||
| } else { | ||||||||||||
| for (auto *F : ProfiledFunctions) { | ||||||||||||
| GuidFilter.insert(Function::getGUIDAssumingExternalLinkage(F->FuncName)); | ||||||||||||
| // Function may have different names in symbol table. Adding | ||||||||||||
|
||||||||||||
| // back all the GUIDs if possible | ||||||||||||
| auto AltGUIDs = AlternativeFunctionGUIDs.equal_range(F); | ||||||||||||
| for (const auto &[_, Func] : make_range(AltGUIDs)) | ||||||||||||
| GuidFilter.insert(Func); | ||||||||||||
| for (auto &Range : F->Ranges) { | ||||||||||||
| auto GUIDs = StartAddrToSymMap.equal_range(Range.first); | ||||||||||||
| for (const auto &[StartAddr, Func] : make_range(GUIDs)) | ||||||||||||
|
|
@@ -604,13 +619,13 @@ bool ProfiledBinary::dissassembleSymbol(std::size_t SI, ArrayRef<uint8_t> Bytes, | |||||||||||
| // Record potential call targets for tail frame inference later-on. | ||||||||||||
| if (InferMissingFrames && FRange) { | ||||||||||||
| uint64_t Target = 0; | ||||||||||||
| MIA->evaluateBranch(Inst, Address, Size, Target); | ||||||||||||
| bool Err = MIA->evaluateBranch(Inst, Address, Size, Target); | ||||||||||||
| if (MCDesc.isCall()) { | ||||||||||||
| // Indirect call targets are unknown at this point. Recording the | ||||||||||||
| // unknown target (zero) for further LBR-based refinement. | ||||||||||||
| MissingContextInferrer->CallEdges[Address].insert(Target); | ||||||||||||
| } else if (MCDesc.isUnconditionalBranch()) { | ||||||||||||
| assert(Target && | ||||||||||||
| assert(Err && | ||||||||||||
| "target should be known for unconditional direct branch"); | ||||||||||||
| // Any inter-function unconditional jump is considered tail call at | ||||||||||||
| // this point. This is not 100% accurate and could further be | ||||||||||||
|
|
@@ -820,6 +835,93 @@ void ProfiledBinary::populateSymbolAddressList(const ObjectFile *Obj) { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void ProfiledBinary::loadSymbolsFromSymtab(const ObjectFile *Obj) { | ||||||||||||
| // Load binary functions from symbol table when Debug info is incomplete. | ||||||||||||
| // Strip the internal suffixes which are not reflected in the DWARF info. | ||||||||||||
| const SmallVector<StringRef, 10> Suffixes( | ||||||||||||
| {// Internal suffixes from CoroSplit pass | ||||||||||||
| ".cleanup", ".destroy", ".resume", | ||||||||||||
| // Internal suffixes from Bolt | ||||||||||||
| ".cold", ".warm", | ||||||||||||
| // Compiler/LTO internal | ||||||||||||
| ".llvm.", ".part.", ".isra.", ".constprop.", ".lto_priv."}); | ||||||||||||
| StringRef FileName = Obj->getFileName(); | ||||||||||||
| for (const SymbolRef &Symbol : Obj->symbols()) { | ||||||||||||
| const SymbolRef::Type Type = unwrapOrError(Symbol.getType(), FileName); | ||||||||||||
| const uint64_t StartAddr = unwrapOrError(Symbol.getAddress(), FileName); | ||||||||||||
| const StringRef Name = unwrapOrError(Symbol.getName(), FileName); | ||||||||||||
| uint64_t Size = 0; | ||||||||||||
| if (isa<ELFObjectFileBase>(Symbol.getObject())) { | ||||||||||||
| ELFSymbolRef ElfSymbol(Symbol); | ||||||||||||
| Size = ElfSymbol.getSize(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (Size == 0 || Type != SymbolRef::ST_Function) | ||||||||||||
| continue; | ||||||||||||
|
|
||||||||||||
| const uint64_t EndAddr = StartAddr + Size; | ||||||||||||
| const StringRef SymName = | ||||||||||||
| FunctionSamples::getCanonicalFnName(Name, Suffixes); | ||||||||||||
| assert(StartAddr < EndAddr && StartAddr >= getPreferredBaseAddress()); | ||||||||||||
|
||||||||||||
|
|
||||||||||||
| auto Range = findFuncRange(StartAddr); | ||||||||||||
| if (!Range) { | ||||||||||||
| assert(findFuncRange(EndAddr - 1) == nullptr); | ||||||||||||
| // Function from symbol table not found previously in DWARF, store ranges. | ||||||||||||
| auto Ret = BinaryFunctions.emplace(SymName, BinaryFunction()); | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a range is found, we should expect function to be found. probably want to |
||||||||||||
| auto &Func = Ret.first->second; | ||||||||||||
| if (Ret.second) { | ||||||||||||
| Func.FuncName = Ret.first->first; | ||||||||||||
| HashBinaryFunctions[MD5Hash(StringRef(SymName))] = &Func; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| Func.HasSymtabName = true; | ||||||||||||
| Func.Ranges.emplace_back(StartAddr, EndAddr); | ||||||||||||
|
|
||||||||||||
| auto R = StartAddrToFuncRangeMap.emplace(StartAddr, FuncRange()); | ||||||||||||
| FuncRange &FRange = R.first->second; | ||||||||||||
|
|
||||||||||||
| FRange.Func = &Func; | ||||||||||||
| FRange.StartAddress = StartAddr; | ||||||||||||
| FRange.EndAddress = EndAddr; | ||||||||||||
|
|
||||||||||||
| } else if (SymName != Range->getFuncName()) { | ||||||||||||
| // Function range already found from DWARF, but the symbol name from | ||||||||||||
| // symbol table is inconsistent with debug info. Log this discrepaency and | ||||||||||||
|
||||||||||||
| // the alternative function GUID. | ||||||||||||
| if (ShowDetailedWarning) | ||||||||||||
| WithColor::warning() | ||||||||||||
| << "Conflicting name for symbol " << Name << " with range (" | ||||||||||||
| << format("%8" PRIx64, StartAddr) << ", " | ||||||||||||
| << format("%8" PRIx64, EndAddr) << ")" | ||||||||||||
| << ", but the DWARF symbol " << Range->getFuncName() | ||||||||||||
| << " indicates an overlapping range (" | ||||||||||||
| << format("%8" PRIx64, Range->StartAddress) << ", " | ||||||||||||
| << format("%8" PRIx64, Range->EndAddress) << ")\n"; | ||||||||||||
|
|
||||||||||||
| assert(StartAddr == Range->StartAddress && EndAddr == Range->EndAddress && | ||||||||||||
| "Mismatched function range"); | ||||||||||||
|
|
||||||||||||
| Range->Func->HasSymtabName = true; | ||||||||||||
|
||||||||||||
| AlternativeFunctionGUIDs.emplace(Range->Func, | ||||||||||||
| MD5Hash(StringRef(SymName))); | ||||||||||||
|
|
||||||||||||
| } else if (StartAddr != Range->StartAddress && | ||||||||||||
| EndAddr != Range->EndAddress) { | ||||||||||||
| // Function already found in DWARF, but the address range from symbol | ||||||||||||
| // table conflicts/overlaps with the debug info. | ||||||||||||
| WithColor::warning() << "Conflicting range for symbol " << Name | ||||||||||||
| << " with range (" << format("%8" PRIx64, StartAddr) | ||||||||||||
| << ", " << format("%8" PRIx64, EndAddr) << ")" | ||||||||||||
| << ", but the DWARF symbol " << Range->getFuncName() | ||||||||||||
| << " indicates another range (" | ||||||||||||
| << format("%8" PRIx64, Range->StartAddress) << ", " | ||||||||||||
| << format("%8" PRIx64, Range->EndAddress) << ")\n"; | ||||||||||||
| llvm_unreachable("invalid function range"); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void ProfiledBinary::loadSymbolsFromDWARFUnit(DWARFUnit &CompilationUnit) { | ||||||||||||
| for (const auto &DieInfo : CompilationUnit.dies()) { | ||||||||||||
| llvm::DWARFDie Die(&CompilationUnit, &DieInfo); | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'm not 100% sure) but I feel this approach might not work.
IIUC, the pseudo probe is not attached to the prologue instruction of the function, so we won't get the probes for the
TargetAddress(unless the prologues are optimized away). Did you see if this condition is hit in your test?I guess then we need another way to find the first/entry probe from the range.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I wonder if we can just fix the FRange-->Func--> funcName in a early time(maybe a central place), the function
getCalleeNameForAddresshere is called for every branch sample, it would be costly. I'm thinking if we can do it earlier by iterating the function ranges just once, say if the range's function is a dwarf-symbol mismatch(i.e. theHasSymtabName), we can find the entry/first probe and use the probe GUID --> function name to fix the function name in the range, then we can avoid the fix here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thanks for checking on that! I updated with a
loadSymbolsFromPseudoProbefunction. That loads the pseudo probe names based on the function range start address and pseudo probe address. I used a lower_bound to get the first pseudo probe of the function address, and traverse up through the inline tree to get the top-level function.