如何通过LLVM或Clang在运行时获取所有全局变量的地址与大小
基于LLVM模块Pass的全局变量运行时追踪实现方案
可行性说明
完全可以通过LLVM模块Pass实现该需求。LLVM模块Pass能够遍历目标程序的所有模块,访问每个全局变量的元信息(地址、类型、大小等),并在编译阶段自动插入运行时代码,完成全局变量边界信息的收集。
核心实现步骤
1. 定义追踪数据结构
先在代码中(或通过Pass自动插入)定义存储全局变量边界的结构体和列表操作逻辑:
#include <stdlib.h> typedef struct { void* start; void* end; } LIST_ITEM; typedef struct Node { LIST_ITEM data; struct Node* next; } ListNode; ListNode* global_list = NULL; void add_global_var(void* start, size_t size) { ListNode* new_node = (ListNode*)malloc(sizeof(ListNode)); new_node->data.start = start; new_node->data.end = (char*)start + size; new_node->next = global_list; global_list = new_node; }
2. 编写LLVM模块Pass
用LLVM C++ API编写模块Pass,遍历所有全局变量,计算其实际占用大小,并生成调用add_global_var的代码,插入到程序初始化阶段(比如main函数开头)。
示例Pass核心代码:
#include "llvm/IR/Module.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/PassManager.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Function.h" #include "llvm/IR/Type.h" #include "llvm/IR/DataLayout.h" using namespace llvm; struct GlobalVarTrackPass : public ModulePass { static char ID; GlobalVarTrackPass() : ModulePass(ID) {} bool runOnModule(Module &M) override { const DataLayout &DL = M.getDataLayout(); // 查找或声明add_global_var函数 FunctionCallee addFunc = M.getOrInsertFunction( "add_global_var", Type::getVoidTy(M.getContext()), Type::getInt8PtrTy(M.getContext()), Type::getSizeTy(M.getContext()) ); Function *MainFunc = M.getFunction("main"); if (!MainFunc) return false; // 在main函数入口插入收集逻辑 IRBuilder<> Builder(&*MainFunc->getEntryBlock().getFirstInsertionPt()); // 遍历模块内所有全局变量 for (GlobalVariable &GV : M.globals()) { if (GV.isDeclaration() || !GV.hasInitializer()) continue; Type *GVType = GV.getValueType(); uint64_t Size = DL.getTypeAllocSize(GVType); Value *GVAddr = Builder.CreatePointerCast(&GV, Type::getInt8PtrTy(M.getContext())); Value *SizeVal = ConstantInt::get(Type::getSizeTy(M.getContext()), Size); Builder.CreateCall(addFunc, {GVAddr, SizeVal}); } return true; } }; char GlobalVarTrackPass::ID = 0; // 注册Pass插件 extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "GlobalVarTrackPass", "v0.1", [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "global-var-track") { MPM.addPass(GlobalVarTrackPass()); return true; } return false; } ); } }; }
3. 编译与应用Pass
- 将Pass代码编译成LLVM插件:
clang++ -fPIC -shared GlobalVarTrackPass.cpp -o libGlobalVarTrackPass.so `llvm-config --cxxflags --ldflags --system-libs --libs core passes`
- 编译目标程序时加载该Pass:
clang your_program.cc -fpass-plugin=./libGlobalVarTrackPass.so -o your_program
关键细节注意
- 外部全局变量:Pass仅处理当前模块定义的全局变量,若需追踪动态库中的全局变量,需对动态库也应用该Pass。
- 字符串字面量:C/C++中的字符串字面量会被识别为全局常量字符数组,Pass会自动捕获,其大小包含末尾的
\0。 - 内存对齐:使用
DataLayout::getTypeAllocSize计算的大小包含内存对齐所需空间,能准确反映全局变量的实际内存占用范围。 - 无main场景适配:若程序没有main函数,可通过LLVM生成全局构造函数(程序启动时自动执行)来完成收集逻辑。
运行效果
编译后的程序启动时,会自动收集所有全局变量的起始地址和结束地址(start + size)并加入global_list,后续可在程序任意位置访问该列表,用于内存越界检测等场景。
内容的提问来源于stack exchange,提问作者Alessio Tullio
相关产品推荐
相关产品推荐

