LLVM IR数组索引检测插桩:动态数组适配方案咨询
适配静态/动态数组的多维索引检测与插桩方案
你之前用LLVM IR层面的getelementptr方案只适配静态数组的问题,核心原因是动态数组在IR层面会被处理为指针操作,没有数组类型的元信息。而Clang AST匹配器是更合适的方案——它直接在抽象语法树层面捕获源代码中的[]索引语法,不管底层是静态数组还是动态指针数组,都能统一处理。
实现思路
源代码中的array[i][j][k]在AST中表现为嵌套的ArraySubscriptExpr节点:最外层是下标为k的ArraySubscriptExpr,其基表达式是下标为j的ArraySubscriptExpr,再内层是下标为i的ArraySubscriptExpr,最底层是数组变量的引用。
我们可以通过以下步骤实现插桩:
- 用AST匹配器捕获所有
ArraySubscriptExpr节点; - 递归遍历嵌套的
ArraySubscriptExpr,收集所有下标表达式和原始数组变量; - 根据索引操作的上下文(独立语句/表达式的一部分),插入或替换为
instrument_array_indexing调用,保证语法正确性。
代码示例(基于Clang Tooling)
#include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" using namespace clang; using namespace clang::ast_matchers; using namespace clang::tooling; class ArrayIndexInstrumenter : public MatchFinder::MatchCallback { public: ArrayIndexInstrumenter(Rewriter &R) : Rewrite(R) {} void run(const MatchFinder::MatchResult &Result) override { const ArraySubscriptExpr *outerASE = Result.Nodes.getNodeAs<ArraySubscriptExpr>("arraySubscript"); if (!outerASE || Result.Context->getDiagnostics().hasErrorOccurred()) { return; } // 递归收集所有下标和原始数组 SmallVector<const Expr*, 4> indices; const Expr *currentBase = outerASE->getBase(); indices.push_back(outerASE->getIndex()); // 遍历嵌套的ArraySubscriptExpr while (const ArraySubscriptExpr *innerASE = dyn_cast<ArraySubscriptExpr>(currentBase)) { indices.push_back(innerASE->getIndex()); currentBase = innerASE->getBase(); } // 反转下标顺序,从最内层到最外层 std::reverse(indices.begin(), indices.end()); const Expr *arrayExpr = currentBase; // 处理带括号的数组表达式 if (const ParenExpr *parenExpr = dyn_cast<ParenExpr>(arrayExpr)) { arrayExpr = parenExpr->getSubExpr(); } // 生成instrument_array_indexing的调用字符串 std::string callStr = "instrument_array_indexing("; callStr += Rewrite.getRewrittenText(arrayExpr->getSourceRange()); for (const Expr *idx : indices) { callStr += ", " + Rewrite.getRewrittenText(idx->getSourceRange()); } callStr += ")"; // 判断当前索引操作是否是独立语句 bool isStandaloneStmt = isa<ExprStmt>(outerASE->getParent()); if (isStandaloneStmt) { // 独立语句:在原语句前插桩调用 SourceLocation insertLoc = outerASE->getBeginLoc(); Rewrite.InsertText(insertLoc, callStr + ";\n", true, true); } else { // 作为表达式的一部分:用逗号表达式包裹,保留原表达式值 std::string replacement = "(" + callStr + ", " + Rewrite.getRewrittenText(outerASE->getSourceRange()) + ")"; Rewrite.ReplaceText(outerASE->getSourceRange(), replacement); } } private: Rewriter &Rewrite; }; static llvm::cl::OptionCategory ToolCategory("Array Index Instrumenter Options"); int main(int argc, const char **argv) { CommonOptionsParser optionsParser(argc, argv, ToolCategory); ClangTool tool(optionsParser.getCompilations(), optionsParser.getSourcePathList()); Rewriter rewriter; ArrayIndexInstrumenter instrumenter(rewriter); MatchFinder finder; // 匹配所有ArraySubscriptExpr节点 finder.addMatcher(arraySubscriptExpr().bind("arraySubscript"), &instrumenter); // 运行工具并初始化Rewriter int result = tool.run(newFrontendActionFactory(&finder, [&rewriter](CompilerInstance &CI) { rewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); }).get()); // 将修改写入文件(或输出到控制台) if (result == 0) { for (const auto &filePath : optionsParser.getSourcePathList()) { const FileEntry *file = rewriter.getSourceMgr().getFileEntryForPath(filePath); if (file) { SourceLocation fileStart = rewriter.getSourceMgr().getLocForStartOfFile(rewriter.getSourceMgr().getMainFileID()); rewriter.getEditBuffer(rewriter.getSourceMgr().getMainFileID()).write(llvm::outs()); } } } return result; }
关键优势
- 统一处理静态/动态数组:不管是
int arr[3][4]这类静态数组,还是int **arr = new int*[3]这类动态指针数组,只要源代码中使用[]索引,都会被ArraySubscriptExpr节点捕获。 - 贴近源代码结构:直接在AST层面操作,不需要关心IR层面的编译转换细节,能准确还原源代码中的下标表达式(比如
arr[i+1][j*2]这类复杂下标)。 - 语法安全:根据索引操作的上下文选择插桩方式,避免在函数参数、赋值表达式中间插入语句导致的语法错误。
注意事项
- 确保
instrument_array_indexing函数已经在代码中声明或包含头文件,避免编译错误。 - 可以通过匹配器的过滤条件(比如
hasBase(hasType(PointerType()))或hasBase(hasType(ConstantArrayType())))针对特定类型的数组进行插桩。 - 对于更复杂的表达式(比如链式调用后的索引
obj.getArray()[i][j]),代码会自动收集完整的基表达式,无需额外修改。
内容的提问来源于stack exchange,提问作者DKay
相关产品推荐
相关产品推荐

