LLVM-Clang 二次開發(fā)--查找全局變量及其調(diào)用函數(shù)

前言

應(yīng)這學(xué)期大作業(yè)要求,完成了基于Clang的二次開發(fā),實現(xiàn)了基于Clang的LibTooling & 庫,編寫ASTMatcher來查找相應(yīng)變量以及調(diào)用函數(shù)。

前期準備

項目初期,由于不清楚后續(xù)的工作,沒有一個總覽,導(dǎo)致走了許多彎路。比如使用 Svn 下載llvm & Clang 的源碼,由于網(wǎng)絡(luò)問題,我們發(fā)現(xiàn)下載得到的源碼始終不完整;再比如通過 Visual Studio + CMake編譯生成項目,生成時間長到難以忍受,等等。

在 Clang 官方文檔 中對 “編寫ASTMatcher” 的介紹中采用了一種新的構(gòu)建工具 - ninja,經(jīng)過查找相關(guān)資料,發(fā)現(xiàn) ninja的build速度非常之快,且具有易用等特點,我們決定參考Clang官方文檔推薦的方式,重新獲取clang源碼,改用 ninja重新構(gòu)建 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 構(gòu)建系統(tǒng)以及 Ninja 構(gòu)建工具。雖然我們的環(huán)境中已經(jīng)裝有 CMake,但為了使二者兼容,我們需要檢查當(dāng)前環(huán)境下 CMake 的版本是否符合要求。

cd /opt/clang-llvm
git clone https://github.com/martine/ninja.git
cd ninja
git checkout release
python bootstrap.py //這里使用了.py文件安裝ninja,意味著我們的環(huán)境中需要安裝python
sudo cp ninja /usr/bin/ //這是linux下的指令,作用是將ninja 添加到環(huán)境變量,windows下只需要將 ninja 路徑添加到環(huán)境變量中即可

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

這之后,我們就可以構(gòu)建 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

在執(zhí)行 ninja 時,我們需要經(jīng)歷漫長的等待(視電腦配置,主要是硬盤的區(qū)別,可能在2~4小時),如果順利的話,我們就成功構(gòu)建好 Clang 了。需要說明的是,這種構(gòu)建方式與 CMake 生成 Visual Studio 解決方案不同點在于,ninja 構(gòu)建之后,會直接在build/bin 路徑下生成若干 .exe文件,這意味著在重新編譯之后我們可以直接使用這些 .exe 文件,是可以脫離Clang 工程的;而Visual Studio + CMake方式,必須在 Visual Studio解決方案中才可以運行。

當(dāng)然,如果不幸在 ninja 指令過后報錯,據(jù)目前發(fā)生過的問題來看,我們需要打開 CMake 的圖形化界面,指令如下。

Cmake -gui

之后為工程指定 CMAKE_CXX_COMPILER & CMAKE_ASM_COMPILER 為 cl.exe(位于Visual Studio/VC/ 目錄下,不同電腦可能有所差別)。

創(chuàng)建 ClangTool

首先我們需要為我們寫的tool 創(chuàng)建一個文件夾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/下創(chuàng)建并打開 CMakeList.txt,寫入以下內(nèi)容:

set(LLVM_LINK_COMPONENTS support)

//此處意味著 經(jīng)過編譯之后,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 已經(jīng)能夠編譯我們的tool了,這里我們就需要在Global-detect.cpp 中寫我們的AST matcher。

ASTMatcher

ASTMatcher 是 Clang 中用來幫助我們實現(xiàn) code-to-code 的轉(zhuǎn)譯或者完成某些查詢的工具。在深入介紹ASTMatcher 前,我們需要先介紹一下Clang中的AST。

AST

欲知詳情,請查閱官方文檔吧(畢竟本文重點不在這)

ASTMatcher簡介

ASTMatcher提供了一個領(lǐng)域特定語言(DSL)來創(chuàng)建基于Clang AST的謂詞,同時支持C++,這意味著允許用戶編寫一個程序來匹配AST節(jié)點并能通過訪問節(jié)點的c++接口來獲取該AST節(jié)點的屬性、源位置等任何信息。

其主要由宏與模板驅(qū)動,用法和 函數(shù)式編程 及其類似。

ASTMatcher用來匹配AST的節(jié)點,它通過調(diào)用構(gòu)造函數(shù)創(chuàng)建,也可以構(gòu)建成一個ASTMatchers的樹,其內(nèi)部可以嵌套多個ASTMatcher,使得匹配更加具體準確。

所有匹配器都是名詞描述實體并且可以綁定,這樣它們就會指向匹配到的內(nèi)容。為此,只需要在這些匹配器中調(diào)用 bind() 方法,例如:

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

創(chuàng)建ASTMatcher

由于Clang AST中有上千個class,我們顯然不可能一個個去看去分析。

這時候我們要清楚一點:使用ASTMatcher的前提是了解你想匹配的AST的樣子。

通常情況下,創(chuàng)建合適的ASTMatcher的策略如下:

  1. 尋找想匹配的節(jié)點的最外層的類
  2. 在 AST Matcher Reference 中查看所寫的Matcher要么匹配到需要的節(jié)點,要么進行"細化"處理
  3. 創(chuàng)建外部匹配表達式,驗證它是否按預(yù)期運行。
  4. 為接下來你想匹配的內(nèi)部節(jié)點檢查匹配器。
  5. 重復(fù)以上步驟,直到完成匹配器。

在我們的項目中,我們采取由一個簡單的.c例子入手,觀察它的AST語法樹,進而總結(jié)全局變量的特性這樣的一種策略。

首先,.c例子如下:

#include<stdio.h>
int a;
int main(){
    a = 1;
    return 0;   
}

調(diào)用clang -cc1 -ast-dump查看其語法樹如下:

|-VarDecl 0xf4a0b8 <F:\1.c:2:1, col:5> col:5 used a 'int'
`-FunctionDecl 0xf4a160 <line:3:1, line:6:1> line:3:5 main 'int ()'
  `-CompoundStmt 0xf4a248 <col:11, line:6:1>
    |-BinaryOperator 0xf4a200 <line:4:1, col:5> 'int' '='
    | |-DeclRefExpr 0xf4a1c8 <col:1> 'int' lvalue Var 0xf4a0b8 'a' 'int'
    | `-IntegerLiteral 0xf4a1e0 <col:5> 'int' 1
    `-ReturnStmt 0xf4a238 <line:5:1, col:8>
      `-IntegerLiteral 0xf4a218 <col:8> 'int' 0

我們可以清楚的看到,全局變量a對應(yīng)的節(jié)點類型為 VarDecl ,引用該變量處的節(jié)點類型為 DeclRefExpr ,而DeclRefExpr 最外層有一層函數(shù),對應(yīng) FunctionDecl節(jié)點類型。

得到這些信息,我們就可以總結(jié)出來"匹配模型"。

對于使用了的全局變量,我們找它的引用,這個引用需要對應(yīng)于一個全局變量聲明,而且引用是在某個函數(shù)內(nèi)部。這種模式下,我們即可得到所有已使用了的全局變量的信息,包括在哪個函數(shù)內(nèi)部調(diào)用。

轉(zhuǎn)換到AST 節(jié)點來看:它首先是一個DeclRefExpr類型節(jié)點,同時它對應(yīng)于一個VarDecl全局節(jié)點,而且這個DeclRefExpr節(jié)點在某個FunctionnDecl下。

因此,我們寫出如下的Matcher:

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

在上述Matcher中,為匹配特定AST節(jié)點,我們把匹配的varDecl節(jié)點綁定到字符串“gvarName”,functionDecl節(jié)點綁定到字符串"function",declRefExpr節(jié)點綁定到字符串"globalReference",以便稍后在匹配回調(diào)中檢索。

獲取匹配節(jié)點

定義了matcher后將需要添加更多的工具來運行它們。Matchers與MatchCallback配對,并注冊一個MatchFinder對象,然后從一個ClangTool運行。

matcher回調(diào)中我們需要對輸入源代碼進行更改。接下來,我們將使用在前面步驟中綁定的節(jié)點。

MatchFinder::run()回調(diào)使用一個MatchFinder:: matchresult& 作為它的參數(shù)。我們最感興趣的是節(jié)點成員,以及如何檢索它們。

由于我們綁定了三個節(jié)點(由“gvarName”、"function"和"globalReference"標識),我們可以通過使用getNodeAs()成員函數(shù)獲得匹配的節(jié)點。

代碼如下:

class Global_Printer : public MatchFinder::MatchCallback {
public:

    virtual void run(const MatchFinder::MatchResult &Result)
    {
        FunctionDecl const * func_decl =
            Result.Nodes.getNodeAs<FunctionDecl>("function");
        Expr const * g_var = Result.Nodes.getNodeAs<Expr>("globalReference");
        VarDecl const * var = Result.Nodes.getNodeAs<VarDecl>("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 << "所在函數(shù):" << 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 <iostream>
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include <fstream>

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<FunctionDecl>("function");
        Expr const * g_var = Result.Nodes.getNodeAs<Expr>("globalReference");
        VarDecl const * var = Result.Nodes.getNodeAs<VarDecl>("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 << "所在函數(shù):" << 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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容