lib/BPFCov.cpp (604 lines of code) (raw):

//===================================================================================================================== // FILE: // BPFCov.cpp // // AUTHOR: // Leonardo Di Donato (leodido) // // DESCRIPTION: // Patch the IR of eBPF programs instrumented for source-code based coverage (-fprofile-instr-generate -fcoverage-mapping). // // USAGE: // 1. Legacy LLVM Pass Manager // opt --load libBPFCov.{so,dylib} [--strip-initializers-only] --bpf-cov <input> // // 2. New LLVM Pass Manager // opt --load-pass-plugin libBPFCov.{so,dylib} --passes='bpf-cov' <input> // // OR // // opt --load-pass-plugin libBPFCov.{so,dylib} --passes='default<O2>' <input> // // NOTICE: CLI options not available when using the new Pass Manager. // // LICENSE: // ... //===================================================================================================================== #include "BPFCov.h" #include "llvm/IR/DIBuilder.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/PassRegistry.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Transforms/IPO/PassManagerBuilder.h" #include "llvm/Transforms/Utils/ModuleUtils.h" #include "llvm/Support/CommandLine.h" static constexpr char PassArg[] = "bpf-cov"; static constexpr char PassName[] = "BPF Coverage Pass"; static constexpr char PluginName[] = "BPFCov"; #define DEBUG_TYPE ::PassArg // NOTE > LLVM_DEBUG requires a LLVM built with NDEBUG unset // NOTE > Then use with opt -debug using namespace llvm; //--------------------------------------------------------------------------------------------------------------------- // CLI options //--------------------------------------------------------------------------------------------------------------------- // This cause the resulting BPF ELF to be readable by llvm-cov for coverage output, // but it does not output a valid BPF program. static cl::opt<bool> StripInitializersOnly( "strip-initializers-only", cl::desc("Stop the pass after the initializers have been removed"), cl::init(false)); //--------------------------------------------------------------------------------------------------------------------- // Utility functions //--------------------------------------------------------------------------------------------------------------------- namespace { bool deleteGVarByName(Module &M, StringRef Name) { auto GV = M.getNamedGlobal(Name); if (!GV) { return false; } errs() << "erasing " << Name << "\n"; GV->eraseFromParent(); return true; } bool deleteFuncByName(Module &M, StringRef Name) { auto F = M.getFunction(Name); if (!F) { return false; } errs() << "erasing " << Name << "()\n"; F->replaceAllUsesWith(UndefValue::get(F->getType())); F->eraseFromParent(); return true; } bool fixupUsedGlobals(Module &M) { auto U = M.getNamedGlobal("llvm.used"); if (!U || !U->hasInitializer()) { return false; } SmallVector<Constant *, 8> UsedGlobals; auto UArray = dyn_cast<ConstantArray>(U->getInitializer()); auto NElems = UArray->getNumOperands(); for (unsigned int i = 0; i < NElems; i++) { if (ConstantExpr *CE = dyn_cast<ConstantExpr>(UArray->getOperand(i))) { auto OC = CE->getOpcode(); if (OC == Instruction::BitCast || OC == Instruction::GetElementPtr) { if (GlobalValue *GV = dyn_cast<GlobalValue>(CE->getOperand(0))) { auto Name = GV->getName(); if (!Name.startswith("__llvm_profile_runtime") && !Name.startswith("__profd") && !Name.startswith("__covrec") && !Name.startswith("__llvm_coverage")) { UsedGlobals.push_back(UArray->getOperand(i)); } } } } // TODO(leodido) > almost certainly the following doesn't make sense for "llvm.used" array else if (GlobalValue *GV = dyn_cast<GlobalValue>(UArray->getOperand(i))) { auto Name = GV->getName(); if (!Name.startswith("__llvm_profile_runtime") && !Name.startswith("__profd") && !Name.startswith("__covrec") && !Name.startswith("__llvm_coverage")) { UsedGlobals.push_back(UArray->getOperand(i)); } } } if (UsedGlobals.size() < NElems) { errs() << "fixing llvm.used\n"; U->eraseFromParent(); ArrayType *AType = ArrayType::get(Type::getInt8PtrTy(M.getContext()), UsedGlobals.size()); U = new GlobalVariable(M, AType, false, GlobalValue::AppendingLinkage, ConstantArray::get(AType, UsedGlobals), "llvm.used"); U->setSection("llvm.metadata"); return true; } return false; } bool swapSectionWithPrefix(Module &M, StringRef Prefix, StringRef New) { bool Changed = false; for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) { GlobalVariable *GV = &*gv_iter; if (GV->hasSection() && GV->getSection().startswith(Prefix)) { errs() << "swapping " << GV->getName() << " section with " << New << " \n"; GV->setSection(New); Changed = true; } } return Changed; } bool convertStructs(Module &M) { bool Changed = false; auto &CTX = M.getContext(); SmallVector<GlobalVariable *, 8> ToDelete; auto CountersSizeAcc = 0; for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) { GlobalVariable *GV = &*gv_iter; if (GV->hasName()) { auto Name = GV->getName(); if (Name.startswith("__profd") && GV->getValueType()->isStructTy()) { errs() << "converting " << Name << " struct to globals\n"; // Translate the function ID to a global scalar ConstantInt *C0 = dyn_cast<ConstantInt>(GV->getInitializer()->getOperand(0)); if (!C0) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } auto Ty = C0->getType(); if (!Ty->isIntegerTy(64)) { // TODO(leodido) > bail out errs() << Name << ": wrong type bandwidth\n"; } auto *GV0 = new GlobalVariable( M, /*Ty=*/Ty, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty, C0->getSExtValue(), true), /*Name=*/Name + ".0", /*InsertBefore=*/GV); GV0->setDSOLocal(true); GV0->setAlignment(MaybeAlign(8)); GV0->setSection(GV->getSection()); appendToUsed(M, GV0); Changed = true; // Translate the function hash to a global scalar ConstantInt *C1 = dyn_cast<ConstantInt>(GV->getInitializer()->getOperand(1)); if (!C1) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } auto Ty1 = C1->getType(); if (!Ty1->isIntegerTy(64)) { // TODO(leodido) > bail out errs() << Name << ": wrong type bandwidth\n"; } auto *GV1 = new GlobalVariable( M, /*Ty=*/Ty1, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty1, C1->getSExtValue(), true), /*Name=*/Name + ".1", /*InsertBefore=*/GV); GV1->setDSOLocal(true); GV1->setAlignment(MaybeAlign(8)); GV1->setSection(GV->getSection()); appendToUsed(M, GV1); // Get the number of counters for current __profd_* ConstantInt *C5 = dyn_cast<ConstantInt>(GV->getInitializer()->getOperand(5)); if (!C5) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } auto Ty5 = C5->getType(); if (!Ty5->isIntegerTy(32)) { // TODO(leodido) > bail out errs() << Name << ": wrong type bandwidth\n"; } auto NumCounters = C5->getSExtValue(); // Translate the address of the counter to a global scalar containing the relative offset auto *GV2 = new GlobalVariable( M, /*Ty=*/Ty1, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty1, CountersSizeAcc, true), /*Name=*/Name + ".2", /*InsertBefore=*/GV); GV2->setDSOLocal(true); GV2->setAlignment(MaybeAlign(8)); GV2->setSection(GV->getSection()); appendToUsed(M, GV2); // Increment the counter offset for the next __profd_* CountersSizeAcc += NumCounters * 8; // Create fake (zero) global scalars for 4th and 5th field of __profd_* structs auto *GV3 = new GlobalVariable( M, /*Ty=*/Ty1, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty1, 0, true), // TODO > we want this or zeroinitializer? /*Name=*/Name + ".3", /*InsertBefore=*/GV); GV3->setDSOLocal(true); GV3->setAlignment(MaybeAlign(8)); GV3->setSection(GV->getSection()); appendToUsed(M, GV3); auto *GV4 = new GlobalVariable( M, /*Ty=*/Ty1, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty1, 0, true), // TODO > we want this or zeroinitializer? /*Name=*/Name + ".4", /*InsertBefore=*/GV); GV4->setDSOLocal(true); GV4->setAlignment(MaybeAlign(8)); GV4->setSection(GV->getSection()); appendToUsed(M, GV4); // Translate the number of counters (that this data refers to) to a global scalar auto NumCountersC = ConstantInt::get(Ty5, NumCounters, true); auto *GV5 = new GlobalVariable( M, /*Ty=*/Ty5, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/NumCountersC, /*Name=*/Name + ".5", /*InsertBefore=*/GV); GV5->setDSOLocal(true); GV5->setAlignment(MaybeAlign(4)); GV5->setSection(GV->getSection()); appendToUsed(M, GV5); // Translate the value sites [2 x i16] into a single i32 auto *GV6 = new GlobalVariable( M, /*Ty=*/Ty5, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantInt::get(Ty5, 0, true), // TODO > obtain from the array values /*Name=*/Name + ".6", /*InsertBefore=*/GV); GV6->setDSOLocal(true); GV6->setAlignment(MaybeAlign(4)); GV6->setSection(GV->getSection()); appendToUsed(M, GV6); ToDelete.push_back(GV); } else if (Name.startswith("__covrec") && GV->getValueType()->isStructTy()) { ToDelete.push_back(GV); } else if (Name.startswith("__llvm_coverage") && GV->getValueType()->isStructTy()) { errs() << "converting " << Name << " struct to globals\n"; ConstantStruct *C0 = dyn_cast<ConstantStruct>(GV->getInitializer()->getOperand(0)); if (!C0) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } auto Ty0 = C0->getType(); if (!Ty0->isStructTy()) { // TODO(leodido) > bail out errs() << Name << ": wrong type\n"; } SmallVector<Constant *, 8> Vals; for (unsigned int i = 0; i < C0->getNumOperands(); i++) { ConstantInt *C = dyn_cast<ConstantInt>(C0->getOperand(i)); if (!C) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } if (!C->getType()->isIntegerTy(32)) { // TODO(leodido) > bail out errs() << Name << ": wrong type\n"; } Vals.push_back(C); } ArrayType *ATy = ArrayType::get(Type::getInt32Ty(CTX), Vals.size()); auto *GV0 = new GlobalVariable( M, /*Ty=*/ATy, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantArray::get(ATy, Vals), /*Name=*/Name + ".0", /*InsertBefore=*/GV); GV0->setDSOLocal(true); GV0->setSection(GV->getSection()); GV0->setAlignment(MaybeAlign(4)); Changed = true; appendToUsed(M, GV0); ConstantDataArray *C1 = dyn_cast<ConstantDataArray>(GV->getInitializer()->getOperand(1)); if (!C1) { // TODO(leodido) > bail out errs() << Name << ": cast failed\n"; } auto Ty1 = C1->getType(); if (!Ty1->isArrayTy()) { // TODO(leodido) > bail out errs() << Name << ": wrong type\n"; } auto *GV1 = new GlobalVariable( M, /*Ty=*/Ty1, /*isConstant=*/true, /*Linkage=*/GlobalVariable::ExternalLinkage, /*Initializer=*/ConstantDataArray::getString(CTX, C1->getRawDataValues(), false), /*Name=*/Name + ".1", /*InsertBefore=*/GV); GV1->setDSOLocal(true); GV1->setAlignment(MaybeAlign(1)); GV1->setSection(GV->getSection()); appendToUsed(M, GV1); ToDelete.push_back(GV); } } } for (auto *GV : ToDelete) { errs() << "erasing " << GV->getName() << "\n"; GV->eraseFromParent(); } return Changed; } bool annotateCounters(Module &M) { bool Annotated = false; DIBuilder DIB(M); Module::debug_compile_units_iterator CUIterator = M.debug_compile_units_begin(); auto *DebugCU = *CUIterator; auto *DebugFile = DebugCU->getFile(); // Save the current list of globals from the CU debug info SmallVector<Metadata *> DebugGlobals; for (auto *DG : DebugCU->getGlobalVariables()) { DebugGlobals.push_back(DG); } for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) { GlobalVariable *GV = &*gv_iter; if (GV->hasName()) { if (GV->getName().startswith("__profc") && GV->getValueType()->isArrayTy()) { // Change to DSO local GV->setLinkage(GlobalValue::LinkageTypes::ExternalLinkage); GV->setDSOLocal(true); auto N = GV->getValueType()->getArrayNumElements(); auto *S64Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); auto *DebugArrayTy = DIB.createArrayType( /*Size=*/N * 64, /*AlignInBits=*/0, /*Ty=*/S64Ty, /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); auto *DebugGVE = DIB.createGlobalVariableExpression( /*Context=*/DebugCU, /*Name=*/GV->getName(), /*LinkageName=*/"", /*File=*/DebugFile, /*LineNo=*/0, /*Ty=*/DebugArrayTy, /*IsLocalToUnit=*/GV->hasLocalLinkage(), /*IsDefinition=*/true, /*Expr=*/nullptr, /*Decl=*/nullptr, /*TemplateParams=*/nullptr, /*AlignInBits=*/0); GV->addDebugInfo(DebugGVE); DebugGlobals.push_back(DebugGVE); Annotated = true; } else if (GV->getName() == "__llvm_prf_nm" && GV->getValueType()->isArrayTy()) { // Change to DSO local GV->setLinkage(GlobalValue::LinkageTypes::ExternalLinkage); GV->setDSOLocal(true); auto N = GV->getValueType()->getArrayNumElements(); auto *S8Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); auto *ConstS8Ty = DIB.createQualifiedType(dwarf::DW_TAG_const_type, S8Ty); auto *DebugArrayTy = DIB.createArrayType( /*Size=*/N * 8, /*AlignInBits=*/0, /*Ty=*/ConstS8Ty, /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); auto *DebugGVE = DIB.createGlobalVariableExpression( /*Context=*/DebugCU, /*Name=*/GV->getName(), /*LinkageName=*/"", /*File=*/DebugFile, /*LineNo=*/0, /*Ty=*/DebugArrayTy, /*IsLocalToUnit=*/GV->hasLocalLinkage(), /*IsDefinition=*/true, /*Expr=*/nullptr, /*Decl=*/nullptr, /*TemplateParams=*/nullptr, /*AlignInBits=*/0); GV->addDebugInfo(DebugGVE); DebugGlobals.push_back(DebugGVE); Annotated = true; } else if (GV->getName().startswith("__profd")) { DIBasicType *Ty; if (GV->getName().endswith(".0") || GV->getName().endswith(".1") || GV->getName().endswith(".2") || GV->getName().endswith(".3") || GV->getName().endswith(".4")) { Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); } else if (GV->getName().endswith(".5") || GV->getName().endswith(".6")) { Ty = DIB.createBasicType("int", 32, dwarf::DW_ATE_signed); } auto *DebugGVE = DIB.createGlobalVariableExpression( /*Context=*/DebugCU, /*Name=*/GV->getName(), /*LinkageName=*/"", /*File=*/DebugFile, /*LineNo=*/0, /*Ty=*/Ty, /*IsLocalToUnit=*/GV->hasLocalLinkage(), /*IsDefinition=*/true, /*Expr=*/nullptr, /*Decl=*/nullptr, /*TemplateParams=*/nullptr, /*AlignInBits=*/0); GV->addDebugInfo(DebugGVE); DebugGlobals.push_back(DebugGVE); Annotated = true; } else if (GV->getName().startswith("__covrec")) { DIType *GVTy; if (GV->getName().endswith(".0")) { auto *Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); GVTy = DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty); } if (GV->getName().endswith(".4")) { auto *Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); auto N = GV->getValueType()->getArrayNumElements(); GVTy = DIB.createArrayType( /*Size=*/N * 8, /*AlignInBits=*/0, /*Ty=*/DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty), /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); } auto *DebugGVE = DIB.createGlobalVariableExpression( /*Context=*/DebugCU, /*Name=*/GV->getName(), /*LinkageName=*/"", /*File=*/DebugFile, /*LineNo=*/0, /*Ty=*/GVTy, /*IsLocalToUnit=*/GV->hasLocalLinkage(), /*IsDefinition=*/true, /*Expr=*/nullptr, /*Decl=*/nullptr, /*TemplateParams=*/nullptr, /*AlignInBits=*/0); GV->addDebugInfo(DebugGVE); DebugGlobals.push_back(DebugGVE); Annotated = true; } else if (GV->getName().startswith("__llvm_coverage")) { auto N = GV->getValueType()->getArrayNumElements(); auto Size = N; DIBasicType *Ty; if (GV->getName().endswith(".0")) { Ty = DIB.createBasicType("int", 32, dwarf::DW_ATE_signed); Size *= 32; } if (GV->getName().endswith(".1")) { Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); Size *= 8; } auto *ConstTy = DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty); auto *DebugArrayTy = DIB.createArrayType( /*Size=*/Size, /*AlignInBits=*/0, /*Ty=*/ConstTy, /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); auto *DebugGVE = DIB.createGlobalVariableExpression( /*Context=*/DebugCU, /*Name=*/GV->getName(), /*LinkageName=*/"", /*File=*/DebugFile, /*LineNo=*/0, /*Ty=*/DebugArrayTy, /*IsLocalToUnit=*/GV->hasLocalLinkage(), /*IsDefinition=*/true, /*Expr=*/nullptr, /*Decl=*/nullptr, /*TemplateParams=*/nullptr, /*AlignInBits=*/0); GV->addDebugInfo(DebugGVE); DebugGlobals.push_back(DebugGVE); Annotated = true; } } } if (Annotated) { errs() << "updating compile unit's globals debug info\n"; DebugCU->replaceGlobalVariables(MDTuple::get(M.getContext(), DebugGlobals)); DIB.finalize(); } return Annotated; } } //--------------------------------------------------------------------------------------------------------------------- // Implementation //--------------------------------------------------------------------------------------------------------------------- PreservedAnalyses BPFCov::run(Module &M, ModuleAnalysisManager &MAM) { bool changed = runOnModule(M); return (changed ? PreservedAnalyses::none() : PreservedAnalyses::all()); } bool BPFCov::runOnModule(Module &M) { errs() << "module: " << M.getName() << "\n"; // LLVM_DEBUG(dbgs() << ""); bool instrumented = false; // Bail out when missing debug info if (M.debug_compile_units().empty()) { errs() << "Missing debug info\n"; return instrumented; } // This sequence is not random at all instrumented |= deleteGVarByName(M, "llvm.global_ctors"); instrumented |= deleteFuncByName(M, "__llvm_profile_init"); instrumented |= deleteFuncByName(M, "__llvm_profile_register_function"); instrumented |= deleteFuncByName(M, "__llvm_profile_register_names_function"); instrumented |= deleteFuncByName(M, "__llvm_profile_runtime_user"); instrumented |= deleteGVarByName(M, "__llvm_profile_runtime"); instrumented |= fixupUsedGlobals(M); // Stop here to avoid rewriting the profiling and coverage structs if (StripInitializersOnly) { return instrumented; } instrumented |= swapSectionWithPrefix(M, "__llvm_prf_cnts", ".data.profc"); instrumented |= swapSectionWithPrefix(M, "__llvm_prf_names", ".rodata.profn"); instrumented |= convertStructs(M); instrumented |= annotateCounters(M); instrumented |= swapSectionWithPrefix(M, "__llvm_prf_data", ".rodata.profd"); instrumented |= swapSectionWithPrefix(M, "__llvm_covmap", ".rodata.covmap"); return instrumented; } //--------------------------------------------------------------------------------------------------------------------- // Legacy PM / Implementation //--------------------------------------------------------------------------------------------------------------------- char LegacyBPFCov::ID = 0; bool LegacyBPFCov::runOnModule(llvm::Module &M) { if (skipModule(M)) { errs() << "legacy: skipping\n"; return false; } errs() << "legacy: running\n"; return Impl.runOnModule(M); } void LegacyBPFCov::print(raw_ostream &OutStream, const Module *) const { OutStream << "BPFCov (Legacy Pass Manager)\n"; } void LegacyBPFCov::getAnalysisUsage(AnalysisUsage &AU) const { // This pass does not transform the control flow graph AU.setPreservesCFG(); } //--------------------------------------------------------------------------------------------------------------------- // New PM / Registration //--------------------------------------------------------------------------------------------------------------------- PassPluginLibraryInfo getBPFCovPluginInfo() { return {LLVM_PLUGIN_API_VERSION, PluginName, LLVM_VERSION_STRING, [](PassBuilder &PB) { // #1 Regiser "opt -passes=bpf-cov" PB.registerPipelineParsingCallback( [&](StringRef Name, ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name.equals(PassArg)) { errs() << "strip-initializers-only: " << (StripInitializersOnly.getValue() ? "true" : "false") << "\n"; MPM.addPass(BPFCov()); return true; } return false; }); // #2 Register for running at "default<O2>" // TODO > double-check PB.registerPipelineStartEPCallback( [&](ModulePassManager &MPM, ArrayRef<PassBuilder::OptimizationLevel> OLevels) { if (OLevels.size() == 1 && OLevels[0] == PassBuilder::OptimizationLevel::O2) { MPM.addPass(BPFCov()); } }); }}; } extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getBPFCovPluginInfo(); } //--------------------------------------------------------------------------------------------------------------------- // Legacy PM / Registration //--------------------------------------------------------------------------------------------------------------------- static RegisterPass<LegacyBPFCov> X(/*PassArg=*/PassArg, /*Name=*/PassName, /*CFGOnly=*/false, /*is_analysis=*/false); static RegisterStandardPasses RegisterBPFCov( PassManagerBuilder::EP_EarlyAsPossible, [](const PassManagerBuilder &, legacy::PassManagerBase &PM) { errs() << "legacy: strip-initializers-only: " << (StripInitializersOnly.getValue() ? "true" : "false") << "\n"; PM.add(new LegacyBPFCov()); });