LLVM-Clang 二次开发--查找全局变量及其调用函数

前言

应这学期大作业要求,完成了基于Clang的二次开发,实现了基于Clang的LibTooling & 库,编写ASTMatcher来查找相应变量以及调用函数。

前期准备

项目初期,由于不清楚后续的工作,没有一个总览,导致走了许多弯路。比如使用 Svn 下载llvm & Clang 的源码,由于网络问题,我们发现下载得到的源码始终不完整;再比如通过 Visual Studio + CMake编译生成项目,生成时间长到难以忍受,等等。

在 Clang 官方文档 中对 “编写ASTMatcher” 的介绍中采用了一种新的构建工具 - ninja,经过查找相关资料,发现 ninja的build速度非常之快,且具有易用等特点,我们决定参考Clang官方文档推荐的方式,重新获取clang源码,改用 ninja重新构建 clang 工程。

下载Clang

弃用 Svn,直接从 GitHub 中抓取 Clang 的源码。

由于 Clang 是 LLVM 工程的一部分,我们首先需要下载 LLVM 的源码,之后再在指定位置下载 Clang 的源码。需要注意的是,虽然我们 git 的只是 LLVM 工程的镜像。

mkdir /opt/clang-llvm && cd /opt/clang-llvm
git clone http://llvm.org/git/llvm.git
cd llvm/tools
git clone http://llvm.org/git/clang.git
cd clang/tools
git clone http://llvm.org/git/clang-tools-extra.git extra

之后,我们需要下载 CMake 构建系统以及 Ninja 构建工具。虽然我们的环境中已经装有 CMake,但为了使二者兼容,我们需要检查当前环境下 CMake 的版本是否符合要求。

cd /opt/clang-llvm
git clone https://github.com/martine/ninja.git
cd ninja
git checkout release
python bootstrap.py //这里使用了.py文件安装ninja,意味着我们的环境中需要安装python
sudo cp ninja /usr/bin/ //这是linux下的指令,作用是将ninja 添加到环境变量,windows下只需要将 ninja 路径添加到环境变量中即可

cd /opt/clang-llvm
git clone git://cmake.org/stage/cmake.git
cd cmake
git checkout next
python bootstrap.py
make
sudo make install

这之后,我们就可以构建 Clang 工程了。

cd /opt/clang-llvm
mkdir build && cd build
cmake -G Ninja ../llvm -DLLVM_BUILD_TESTS=ON  // Enable tests; default is off.

ninja
ninja install

在执行 ninja 时,我们需要经历漫长的等待(视电脑配置,主要是硬盘的区别,可能在2~4小时),如果顺利的话,我们就成功构建好 Clang 了。需要说明的是,这种构建方式与 CMake 生成 Visual Studio 解决方案不同点在于,ninja 构建之后,会直接在build/bin 路径下生成若干 .exe文件,这意味着在重新编译之后我们可以直接使用这些 .exe 文件,是可以脱离Clang 工程的;而Visual Studio + CMake方式,必须在 Visual Studio解决方案中才可以运行。

当然,如果不幸在 ninja 指令过后报错,据目前发生过的问题来看,我们需要打开 CMake 的图形化界面,指令如下。

Cmake -gui

之后为工程指定 CMAKE_CXX_COMPILER & CMAKE_ASM_COMPILER 为 cl.exe(位于Visual Studio/VC/ 目录下,不同电脑可能有所差别)。

创建 ClangTool

首先我们需要为我们写的tool 创建一个文件夹Global-detect,并且“告知” CMake存在文件夹Global-detect。因为这显然不是Clang 的核心工具,因此我们需要将文件夹放在 tools/extra/ 路径下。

cd /opt/clang-llvm/llvm/tools/clang
mkdir tools/extra/Global-detect

//以下通过图形化界面操作
打开tools/extra/CMakeLists.txt
在合适的位置加入 add_subdirectory(Global-detect)

在tools/extra/Global-detect/下创建并打开 CMakeList.txt,写入以下内容:

set(LLVM_LINK_COMPONENTS support)

//此处意味着 经过编译之后,Global-detect.cpp的功能会"集成"到 global-detect.exe中,该.exe文件会在/build/bin路径下生成
add_clang_executable(global-detect 
  Global-detect.cpp
  )
//此处声明 global-detect.exe 的链接库
target_link_libraries(global-detect
  clangTooling
  clangBasic
  clangASTMatchers
  )

到此,Ninja 已经能够编译我们的tool了,这里我们就需要在Global-detect.cpp 中写我们的AST matcher。

ASTMatcher

ASTMatcher 是 Clang 中用来帮助我们实现 code-to-code 的转译或者完成某些查询的工具。在深入介绍ASTMatcher 前,我们需要先介绍一下Clang中的AST。

AST

欲知详情,请查阅官方文档吧(毕竟本文重点不在这)

ASTMatcher简介

ASTMatcher提供了一个领域特定语言(DSL)来创建基于Clang AST的谓词,同时支持C++,这意味着允许用户编写一个程序来匹配AST节点并能通过访问节点的c++接口来获取该AST节点的属性、源位置等任何信息。

其主要由宏与模板驱动,用法和 函数式编程 及其类似。

ASTMatcher用来匹配AST的节点,它通过调用构造函数创建,也可以构建成一个ASTMatchers的树,其内部可以嵌套多个ASTMatcher,使得匹配更加具体准确。

所有匹配器都是名词描述实体并且可以绑定,这样它们就会指向匹配到的内容。为此,只需要在这些匹配器中调用 bind() 方法,例如:

variable(hasType(isInteger())).bind("intvar")

创建ASTMatcher

由于Clang AST中有上千个class,我们显然不可能一个个去看去分析。

这时候我们要清楚一点:使用ASTMatcher的前提是了解你想匹配的AST的样子。

通常情况下,创建合适的ASTMatcher的策略如下:

  1. 寻找想匹配的节点的最外层的类
  2. 在 AST Matcher Reference 中查看所写的Matcher要么匹配到需要的节点,要么进行”细化”处理
  3. 创建外部匹配表达式,验证它是否按预期运行。
  4. 为接下来你想匹配的内部节点检查匹配器。
  5. 重复以上步骤,直到完成匹配器。

在我们的项目中,我们采取由一个简单的.c例子入手,观察它的AST语法树,进而总结全局变量的特性这样的一种策略。

首先,.c例子如下:

#include
int a;
int main(){
    a = 1;
    return 0;   
}

调用clang -cc1 -ast-dump查看其语法树如下:

|-VarDecl 0xf4a0b8  col:5 used a 'int'
`-FunctionDecl 0xf4a160  line:3:5 main 'int ()'
  `-CompoundStmt 0xf4a248 
    |-BinaryOperator 0xf4a200  'int' '='
    | |-DeclRefExpr 0xf4a1c8  'int' lvalue Var 0xf4a0b8 'a' 'int'
    | `-IntegerLiteral 0xf4a1e0  'int' 1
    `-ReturnStmt 0xf4a238 
      `-IntegerLiteral 0xf4a218  'int' 0

我们可以清楚的看到,全局变量a对应的节点类型为 VarDecl ,引用该变量处的节点类型为 DeclRefExpr ,而DeclRefExpr 最外层有一层函数,对应 FunctionDecl节点类型。

得到这些信息,我们就可以总结出来”匹配模型”。

对于使用了的全局变量,我们找它的引用,这个引用需要对应于一个全局变量声明,而且引用是在某个函数内部。这种模式下,我们即可得到所有已使用了的全局变量的信息,包括在哪个函数内部调用。

转换到AST 节点来看:它首先是一个DeclRefExpr类型节点,同时它对应于一个VarDecl全局节点,而且这个DeclRefExpr节点在某个FunctionnDecl下。

因此,我们写出如下的Matcher:

StatementMatcher GlobalVarMatcher = declRefExpr(
    to(
        varDecl(
            hasGlobalStorage()
        ).bind("gvarName")
    ) // to
    , hasAncestor(
        functionDecl().bind("function")
    )
).bind("globalReference");

在上述Matcher中,为匹配特定AST节点,我们把匹配的varDecl节点绑定到字符串“gvarName”,functionDecl节点绑定到字符串”function”,declRefExpr节点绑定到字符串”globalReference”,以便稍后在匹配回调中检索。

获取匹配节点

定义了matcher后将需要添加更多的工具来运行它们。Matchers与MatchCallback配对,并注册一个MatchFinder对象,然后从一个ClangTool运行。

matcher回调中我们需要对输入源代码进行更改。接下来,我们将使用在前面步骤中绑定的节点。

MatchFinder::run()回调使用一个MatchFinder:: matchresult& 作为它的参数。我们最感兴趣的是节点成员,以及如何检索它们。

由于我们绑定了三个节点(由“gvarName”、”function”和”globalReference”标识),我们可以通过使用getNodeAs()成员函数获得匹配的节点。

代码如下:

class Global_Printer : public MatchFinder::MatchCallback {
public:

    virtual void run(const MatchFinder::MatchResult &Result)
    {
        FunctionDecl const * func_decl =
            Result.Nodes.getNodeAs("function");
        Expr const * g_var = Result.Nodes.getNodeAs("globalReference");
        VarDecl const * var = Result.Nodes.getNodeAs("gvarName");
        if (func_decl && var) {
            /*
            DeclarationNameInfo NameInfo = func_decl->getNameInfo();
            DeclarationName Name = NameInfo.getName();
            */
            cout << "变量名:";
            DeclarationName Name2 = var->getDeclName();
            cout << var->getNameAsString() << "\n";
            int paraNum = func_decl->getNumParams();
            cout << "全局变量类型:" << var->getType().getAsString() << "    ";
            cout << "所在函数:" << func_decl->getCallResultType().getAsString() << "  " << func_decl->getNameAsString();
            cout << "(";
            if (paraNum != 0)
            {
                for (int i = 0; i < paraNum; i++)
                {
                    if (i != 0) cout << ",";
                    cout << func_decl->getParamDecl(i)->getType().getAsString() << " ";
                    cout << func_decl->getParamDecl(i)->getNameAsString();
                }
            }
            cout << ")" << endl;
            //Name2.dump();
        }
    }
};

int main(int argc, const char **argv) {
    CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
    ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
    Global_Printer GvarPrinter;
    MatchFinder Finder;
    Finder.addMatcher(GlobalVarMatcher, &GvarPrinter);
    freopen("F://out.txt", "w", stdout);
    Tool.run(newFrontendActionFactory(&Finder).get());
    Finder.~MatchFinder();
    return 0;
}

完整代码

#include 
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include 

using namespace std;
using namespace clang::tooling;
using namespace llvm;
static llvm::cl::OptionCategory MyToolCategory("global-detect options");

#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang;
using namespace clang::ast_matchers;

StatementMatcher GlobalVarMatcher = declRefExpr(
    to(
        varDecl(
            hasGlobalStorage()
        ).bind("gvarName")
    ) // to
    , hasAncestor(
        functionDecl().bind("function")
    )
).bind("globalReference");


class Global_Printer : public MatchFinder::MatchCallback {
public:

    virtual void run(const MatchFinder::MatchResult &Result)
    {
        FunctionDecl const * func_decl =
            Result.Nodes.getNodeAs("function");
        Expr const * g_var = Result.Nodes.getNodeAs("globalReference");
        VarDecl const * var = Result.Nodes.getNodeAs("gvarName");
        if (func_decl && var) {
            /*
            DeclarationNameInfo NameInfo = func_decl->getNameInfo();
            DeclarationName Name = NameInfo.getName();
            */
            cout << "变量名:";
            DeclarationName Name2 = var->getDeclName();
            cout << var->getNameAsString() << "\n";
            int paraNum = func_decl->getNumParams();
            cout << "全局变量类型:" << var->getType().getAsString() << "    ";
            cout << "所在函数:" << func_decl->getCallResultType().getAsString() << "  " << func_decl->getNameAsString();
            cout << "(";
            if (paraNum != 0)
            {
                for (int i = 0; i < paraNum; i++)
                {
                    if (i != 0) cout << ",";
                    cout << func_decl->getParamDecl(i)->getType().getAsString() << " ";
                    cout << func_decl->getParamDecl(i)->getNameAsString();
                }
            }
            cout << ")" << endl;
            //Name2.dump();
        }
    }
};

int main(int argc, const char **argv) {
    CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
    ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
    Global_Printer GvarPrinter;
    MatchFinder Finder;
    Finder.addMatcher(GlobalVarMatcher, &GvarPrinter);
    freopen("F://out.txt", "w", stdout);
    Tool.run(newFrontendActionFactory(&Finder).get());
    Finder.~MatchFinder();
    return 0;
}

你可能感兴趣的:(其他)