核心内容摘要
农民伯伯下乡妹:一场跨越山海的深情遇见
环境背景
虚拟机 Ubuntu
20.
llvm
15.
0.
目标在你的虚拟机中编写、编译并运行一个自定义的 Clang Static Analyzer Checker使其能对一个简单的 C 文件输出自定义的警告信息。
具体实现
环境配置克隆 LLVM
15.
7 源码cd ~ git clone --branch llvmorg-
15.
7 --depth 1 https://github.com/llvm/llvm-project.git在llvm-project目录下创建普通 build 目录非build-arm进行 x86/x64 架构的 CMake 配置和编译cd ~/llvm-project mkdir -p build cd build # CMake 配置普通编译用于开发调试 cmake -G Ninja \ -DCMAKE_BUILD_TYPEDebug \ -DLLVM_ENABLE_PROJECTSclang \ -DLLVM_ENABLE_RTTION \ -DLLVM_ENABLE_EHON \ ../llvm # 首次编译 Clang耗时
小时后续增量编译只需几分钟 ninja clang检查是否生成了 clang 可执行文件# 查看 bin 目录下是否有 clang、clang 可执行文件 ls -l bin/ | grep clang验证通过标志终端输出类似如下内容能看到clang和clang权限列包含x表示可执行-rwxr-xr-x 2 user user xxxxxx 月 xx xx:xx clang -rwxr-xr-x 2 user user xxxxxx 月 xx xx:xx clang
在源码树中创建你的 Checker编译验证通过后就可以按照我们之前拆分的流程开始创建和注册你的 Checker 了核心步骤快速回顾对应之前的细化内容这里简化为执行路径阶段 1创建 Checker 源码文件
进入 Checkers 源码目录直接复制执行cd ~/llvm-project/clang/lib/StaticAnalyzer/Checkers/
新建并编写SimpleGraduationChecker.cppnano SimpleGraduationChecker.cpp这是一个最小可行性代码。
它不涉及复杂指针逻辑仅仅检测代码中是否出现了名为test_function的函数调用。
如果出现了就报一个警。
#include clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h #include clang/StaticAnalyzer/Core/BugReporter/BugType.h #include clang/StaticAnalyzer/Core/Checker.h #include clang/StaticAnalyzer/Core/CheckerManager.h #include clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h using namespace clang; using namespace ento; namespace { // 定义一个Checker类继承自Checker模板 // PreCall 表示我们要监听“函数调用前”这个事件 class SimpleGraduationChecker : public Checkercheck::PreCall { // 定义一个 BugType用于分类报警 mutable std::unique_ptrBugType BT; public: // 这是核心回调函数每次遇到函数调用都会进来 void checkPreCall(const CallEvent Call, CheckerContext C) const { //
获取被调用函数的标识符 const IdentifierInfo *ID Call.getCalleeIdentifier(); if (!ID) return; //
检查函数名是不是 test_function if (ID-getName() test_function) { //
如果是准备报警 if (!BT) BT.reset(new BugType(this, Graduation Project Check, Custom Error)); //
创建一个 BugReport // GenerateNonFatalErrorNode 创建一个解释节点表示这里出了问题 ExplodedNode *N C.generateNonFatalErrorNode(); if (!N) return; auto Report std::make_uniquePathSensitiveBugReport( *BT, Warning: Found a dangerous call to test_function!, N); // 标记出错的代码位置 Report-addRange(Call.getSourceRange()); // 提交报告 C.emitReport(std::move(Report)); } } }; } // end anonymous namespace // 注册逻辑照抄即可 void ento::registerSimpleGraduationChecker(CheckerManager mgr) { mgr.registerCheckerSimpleGraduationChecker(); } // 这是一个元数据函数用于查询Checker是否应该被启用通常直接返回true bool ento::shouldRegisterSimpleGraduationChecker(const CheckerManager mgr) { return true; }阶段 2修改两个配置文件注册 Checker
修改同目录下的CMakeLists.txtnano CMakeLists.txt搜索.cpp找到文件列表添加SimpleGraduationChecker.cpp在文件开头的头文件列表中添加#include clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h保存退出CtrlO→ 回车 →CtrlX
修改Checkers.td注册表文件注册 Checker 名称cd ~/llvm-project/clang/include/clang/StaticAnalyzer/Checkers/ nano Checkers.td阶段 3增量编译 Clang进入build目录cd ~/llvm-project/build进入build目录cd ~/llvm-project/build先构建自动生成文件和头文件目标这一步会生成OMP.inc等文件耗时几分钟ninja llvm-headers clang-headers若上述命令无报错再额外构建clangStaticAnalyzerCheckers目标你的 Checker 属于这个模块直接构建该模块更精准ninja clangStaticAnalyzerCheckers最后再增量编译clang确保所有依赖都生效ninja clang验证是否编译成功验证 Checker 是否注册成功~/llvm-project/build/bin/clang -cc1 -analyzer-checker-help | grep graduation预计输出如下输出graduation.SimpleCheck3 .运行测试编译完成后你的bin/clang就已经包含了你的 Checker。
准备测试代码创建一个test.c文件这里我存在了~/Desktop/test.c下void test_function() {} void safe_function() {} int main() { safe_function(); test_function(); // 这一行应该被报错 return 0; }执行分析命令使用-analyzer-checker参数指定我们在Checkers.td里定义的名字假设是graduation.SimpleCheck~/llvm-project/build/bin/clang -cc1 -analyze -analyzer-checkergraduation.SimpleCheck ~/Desktop/test.c执行结果如下到此为止你已经成功打通了 Clang Static AnalyzerCSA的完整开发流程从源码修改、配置注册、增量编译到最终运行自定义检查器并输出警告整个链路都跑通了。