LLVM Pass开发:如何定位全局变量首次使用位置并应用转换
定位全局变量首次使用并执行一次性转换的LLVM方案
针对你的需求——在全局变量首次执行使用前仅执行一次转换,LLVM没有直接提供全局范围的DominatorTree,但可以通过调用图分析+函数内支配树的组合方案实现,完全不需要创建预处理存根函数。
核心思路
- 先确定程序的可达函数(从入口点如
main出发,通过调用图遍历所有会被执行到的函数),过滤掉未被调用的死代码中的全局变量使用。 - 对每个可达函数,收集其中目标全局变量的所有使用指令。
- 结合函数内的DominatorTree,找到该函数内所有全局变量使用的公共支配块——也就是所有执行路径中一定会最先到达的代码位置,在该位置前插入转换逻辑。
- 一旦找到第一个符合条件的插入点并完成转换,立即标记该全局变量为已处理,后续所有使用都跳过操作。
具体实现步骤
1. 构建调用图,遍历可达函数
通过LLVM的CallGraphWrapperPass获取模块的调用图,从入口函数(通常是main)出发,广度优先或深度优先遍历所有可达函数,避免处理死代码。
2. 收集函数内的全局变量使用指令
对每个可达函数,遍历其所有基本块和指令,筛选出加载/存储目标全局变量的指令(比如LoadInst或StoreInst)。
3. 利用支配树定位最早插入点
对包含全局变量使用的函数,构建DominatorTree,找到所有使用指令所在基本块的最近公共支配块——这个块是所有执行路径中必然会先于任何全局变量使用到达的位置,在该块的首个非PHI指令前插入转换代码即可保证在首次使用前执行。
代码示例(LLVM Pass)
#include "llvm/Analysis/CallGraph.h" #include "llvm/Analysis/DominatorTree.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instructions.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" #include <queue> #include <set> #include <vector> using namespace llvm; namespace { struct GlobalTransformPass : public ModulePass { static char ID; GlobalTransformPass() : ModulePass(ID) {} bool runOnModule(Module &M) override { // 目标全局变量 GlobalVariable *TargetGV = M.getNamedGlobal("MyGlobal"); if (!TargetGV || TargetGV->isDeclaration()) return false; // 获取程序入口函数(这里假设是main,可根据场景调整为_start等) Function *EntryFunc = M.getFunction("main"); if (!EntryFunc) return false; // 获取调用图 CallGraph &CG = getAnalysis<CallGraphWrapperPass>().getCallGraph(); bool IsGVProcessed = false; std::set<Function*> VisitedFuncs; std::queue<Function*> Worklist; Worklist.push(EntryFunc); VisitedFuncs.insert(EntryFunc); while (!Worklist.empty() && !IsGVProcessed) { Function *CurrentFunc = Worklist.front(); Worklist.pop(); if (CurrentFunc->isDeclaration()) continue; // 收集当前函数中所有使用目标全局变量的指令 std::vector<Instruction*> GVUses; for (BasicBlock &BB : *CurrentFunc) { for (Instruction &I : BB) { // 匹配加载全局变量的指令,可扩展到存储或其他使用场景 if (LoadInst *LI = dyn_cast<LoadInst>(&I)) { if (LI->getPointerOperand() == TargetGV) { GVUses.push_back(LI); } } } } if (GVUses.empty()) { // 当前函数不使用目标GV,继续遍历它调用的函数 for (auto &Edge : CG[CurrentFunc]) { Function *Callee = Edge.second->getFunction(); if (Callee && !VisitedFuncs.count(Callee)) { VisitedFuncs.insert(Callee); Worklist.push(Callee); } } continue; } // 构建当前函数的支配树,找到所有使用点的公共支配块 DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(*CurrentFunc).getDomTree(); BasicBlock *CommonDomBB = GVUses[0]->getParent(); for (Instruction *Use : GVUses) { CommonDomBB = DT.findNearestCommonDominator(CommonDomBB, Use->getParent()); } // 确定插入位置:跳过PHI节点,选块内第一个可插入指令的位置 Instruction *InsertPt = &CommonDomBB->front(); while (isa<PHINode>(InsertPt)) { InsertPt = InsertPt->getNextNode(); } // 在这里插入你的转换逻辑 // 示例:插入一个自定义转换函数的调用 // Function *TransformFunc = M.getFunction("TransformMyGlobal"); // if (TransformFunc) { // CallInst::Create(TransformFunc, {}, "", InsertPt); // } errs() << "已在函数" << CurrentFunc->getName() << "的基本块" << CommonDomBB->getName() << "中插入全局变量转换逻辑\n"; // 标记全局变量已处理,终止遍历 IsGVProcessed = true; break; } return IsGVProcessed; } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired<CallGraphWrapperPass>(); AU.addRequired<DominatorTreeWrapperPass>(); AU.setPreservesCFG(); } }; } // namespace char GlobalTransformPass::ID = 0; static RegisterPass<GlobalTransformPass> X("global-transform", "全局变量首次使用前转换Pass");
关键注意事项
- 间接调用处理:如果程序存在函数指针等间接调用,CallGraph可能无法完全捕获,此时可选择保守处理(假设所有函数都是可达的),或结合指针分析增强调用图准确性。
- 多线程场景:如果程序是多线程的,首次使用可能在多个线程中触发,需要额外添加线程同步逻辑(如原子操作、锁)保证转换仅执行一次。
- 入口点适配:非标准程序(如嵌入式、内核)需将入口函数从
main改为对应启动函数(如_start)。
内容的提问来源于stack exchange,提问作者d00rt
相关产品推荐
相关产品推荐

