手写静态分析器Checker
1.简介
目前网上找到的一些构建Checker的方法都比较古老,随着版本更新一些库文件和依赖文件的变动老方法不太适用,所以整理了一下在10.0版本下构建Checker的方法,10.0之前没没有使用过,后面的版本应该大差不差了。
2.编写Checker
编写自定义的Checker需要在以下目录进行:
llvm/tools/clang/lib/StaticAnalyzer/Checkers
此目录存放的是当前静态分析其所有的Checker文件。
新建MyPathChecker.cpp文件并编写如下代码(实现了对扩展图的DFS,代码是师弟编写的)
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Analysis/Analyses/Dominators.h"
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/CallGraph.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
#include "llvm/Support/Process.h"
#include <vector>
using namespace clang;
using namespace ento;
namespace {
class EGraphHelper {
private:
std::vector<ExplodedNode*> path;
std::vector<std::vector<ExplodedNode*>> paths;
public:
void dfsTrave(ExplodedGraph &G) {
llvm::outs() << "start dfs\n";
path.clear();
paths.clear();
// 遍历所有根节点
for (auto it = G.roots_begin(); it < G.roots_end(); it++) {
// 指向ExplodedNode*指针
path.push_back(*it);
dfs(*it);
path.pop_back();
}
pathTrim(paths);
}
void dfs(ExplodedNode* node) {
// ProgramStateRef state = node->getState();
// int id = state->getID();
// llvm::outs() << id << "\n";
// llvm::outs() << "ptr_addr: " << node << "\n";
// 搜索到路径终止处
if (node->getFirstSucc() == nullptr) {
// 存储当前路径信息
paths.push_back(path);
return;
}
for (auto it = node->succ_begin(); it < node->succ_end(); it++) {
path.push_back(*it);
dfs(*it);
path.pop_back();
}
}
void pathTrim(std::vector<std::vector<ExplodedNode*>> &paths) {
int pathNum = 0;
for (auto path : paths) {
llvm::outs() << "path-" << ++pathNum << "\n";
for (auto node : path) {
// ProgramStateRef state = node->getState();
// int id = state->getID();
// llvm::outs() << id << " ";
ProgramPoint point = node->getLocation();
llvm::Optional<BlockEntrance> be = point.getAs<BlockEntrance>();
if (be != None) {
// 收集basic block路径信息
auto blockId = be->getBlock()->getBlockID();
llvm::outs() << blockId << " ";
}
}
llvm::outs() << "\n";
}
}
}; // end class EGraphHelper
class MyPathChecker : public Checker< check::EndAnalysis > {
public:
MyPathChecker() {}
void checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const;
};// end class MyPathChecker
}// end namespace
// 调用的回调函数
void MyPathChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const {
llvm::outs() << "get path check\n";
EGraphHelper helper;
helper.dfsTrave(G);
}
void ento::registerMyPathChecker(CheckerManager &mgr) {
mgr.registerChecker<MyPathChecker>();
}
bool ento::shouldRegisterMyPathChecker(const LangOptions &LO) {
return true;
}
其中 EGraphHelper 是一个自定义工具类,主要是MyPathChecker 这个类,继承Checker类并在registerMyPathChecker中注册,shouldRegisterMyPathChecker这个个人理解是判断是否能注册此Checker,此函数存在一个问题:
bool ento::shouldRegisterMyPathChecker(const LangOptions &LO) {
return true;
}
函数参数在15.0版本编译的时候应该指定如下格式:
bool ento::shouldRegisterMyPathChecker(const CheckerManager &mgr) {
return true;
}
我在10.0版本编译的时候用第二种格式会报以下错误,具体原因不太清楚,可能是版本的问题,对照了源码中的方法使用第一种格式可以编译成功:
3.配置CMakeLists
如下路径配置CMakeLists,添加刚才编写的cpp文件即可
llvm/tools/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
4.配置Checkers.td
这个是非常重要的一步,这个配置是将自定义的Checker整合到静态分析器中。
如下路径配置Checkers.td,注意:网上很多资料此路径是在lib下,应该是版本比较老的代码结构了,目前此文件在include路径下。
llvm/tools/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
此文件结构如下,ParentPackage 标识当前Checker属于哪一个包(debug、core等),我们把刚才编写的checker放在了Debug中,如下。
//===----------------------------------------------------------------------===//
// Debugging checkers (for analyzer development).
//===----------------------------------------------------------------------===//
let ParentPackage = Debug in {
def ***
def ***
***
// 在源文件的Debug中添加如下代码即可
def MyPathChecker : Checker<"MyPathChecker">,
HelpText<"Check for get path">,
Documentation<NotDocumented>;
}
5.编译构建
上述所有步骤准备完成之后可以构建程序了,进入最外层的build目录make然后make install就完事啦!这里就不放图片啦。
6.测试
命令如下,debug.MyPathChecker就是我们刚才编写的checker,两个-I指定了动态链接库,根据本机做调整即可,最后test.c是测试的源代码:
clang -cc1 -I /usr/include -I /home/zzh/llvm/lib/clang/10.0.0/include -analyze -analyzer-checker=debug.MyPathChecker test.c
生成了扩展图中的路径信息,如下: