1. 程式人生 > >clang 開發應用xcode 編譯檢查的外掛 一:構建篇

clang 開發應用xcode 編譯檢查的外掛 一:構建篇

Clang是llvm的編譯器前端,非常適合進行原始碼分析.目前開源的oclint就是基於clang進行的程式碼靜態檢查.工作中遇到了一些問題需要進行程式碼分析,所以學習了外掛的開發流程.既然開發外掛就要有合適的IDE,Mac上最合適的無疑是xcode了.本文將講述如何使用xcode開發clang外掛,在此之前請先了解clang的相關知識.

一、搭建環境

1.獲取Clang原始碼

由於是要使用到Xcode中,因此最好還是從蘋果官網中獲取LLVM的原始碼,目前版本是Xcode8.1下的LLVM的原始碼
https://opensource.apple.com/tarballs/clang/clang-800.0.42.1.tar.gz


PS:
a.檢視xcode對應的clang版本 https://trac.macports.org/wiki/XcodeVersionInfo
b.官網查詢對應釋出版本 https://opensource.apple.com/source/clang/

這不只是Clang部分的原始碼,其中LLVM主要的子專案包括:

  • LLVM Core: 包含一個現在的原始碼/目標裝置無關的優化器,一集一個針對很多主流(甚至於一些非主流)的CPU的彙編程式碼生成支援。包含一個現在的原始碼/目標裝置無關的優化器,一集一個針對很多主流(甚至於一些非主流)的CPU的彙編程式碼生成支援。

  • Clang: 一個C/C++/Objective-C編譯器,致力於提供令人驚訝的快速編譯,極其有用的錯誤和警告資訊,提供一個可用於構建很棒的原始碼級別的工具.

  • dragonegg gcc外掛,可將GCC的優化和程式碼生成器替換為LLVM的相應工具。

  • LLDB: 基於LLVM提供的庫和Clang構建的優秀的本地偵錯程式。 libc++、libc++
    ABI 符合標準的,高效能的C++標準庫實現,以及對C++11的完整支援。

  • compiler-rt: 針對”__fixunsdfdi”和其他目標機器上沒有一個核心IR(intermediate
    representation)對應的短原生指令序列時,提供高度調優過的底層程式碼生成支援。

  • OpenMP: Clang中對多平臺並行程式設計的runtime支援。 vmkit: 基於LLVM的Java和.NET虛擬機器實現

  • polly :支援高級別的迴圈和資料本地化優化支援的LLVM框架。 libclc: OpenCL(開放運算語言)標準庫的實現

  • klee: 基於LLVM編譯基礎設施的符號化虛擬機器 SAFECode: 記憶體安全的C/C++編譯器

  • lld :clang/llvm內建的連結器

2.編譯外掛

下載原始碼完成後解壓目錄,接下來就是要做編譯LLVM的工作了。

找到 [解壓llvm目錄]/src/tools/clang/examples 目錄,在裡面新建一個目錄如MyPlugin。然後修改example目錄的CMakeLists.txt檔案,新增一項:

add_subdirectory(MyPlugin)

然後進入建立的MyPlugin目錄,建立三個檔案,分別是:

CMakeList.txt
MyPlugin.cpp
MyPlugin.exports

然後在新建的CMakeList.txt中加入下面內容:

# If we don't need RTTI or EH, there's no reason to export anything
# from the plugin.
if( NOT MSVC ) # MSVC mangles symbols differently
  if( NOT LLVM_REQUIRES_RTTI )
    if( NOT LLVM_REQUIRES_EH )
      set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/MyPlugins.exports)
    endif()
  endif()
endif()

add_llvm_loadable_module(MyPlugin MyPlugin.cpp)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  target_link_libraries(MyPlugin ${cmake_2_8_12_PRIVATE}
    clangAST
    clangBasic
    clangFrontend
    LLVMSupport
    )
endif()

然後在外掛檔案MyPlugin.cpp中,新增下面的內容:

#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"

using namespace clang;

namespace
{
    class MyPluginConsumer : public ASTConsumer
    {
    CompilerInstance &Instance;
    std::set<std::string> ParsedTemplates;
    public:
        MyPluginConsumer(CompilerInstance &Instance,
                               std::set<std::string> ParsedTemplates)
        : Instance(Instance), ParsedTemplates(ParsedTemplates) {}
    };

    class MyPluginASTAction : public PluginASTAction
    {
    std::set<std::string> ParsedTemplates;
    protected:
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                       llvm::StringRef) override
        {
            return llvm::make_unique<MobCodeConsumer>(CI, ParsedTemplates);
        }

        bool ParseArgs(const CompilerInstance &CI,
                       const std::vector<std::string> &args) override {

            DiagnosticsEngine &D = CI.getDiagnostics();
            D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
                                       “My plugin Started..."));

            return true;
        }
    };
}

static clang::FrontendPluginRegistry::Add<MyPluginASTAction>
X("MyPlugin", “My plugin");

上面的程式碼中主要先看看MyPluginASTAction的ParseArgs方法,這是一個外掛的入口函式,在這個方法裡面呼叫了一個叫DiagnosticsEngine物件的Report方法,這段程式碼的主要功能是向編譯器報告一個錯誤,而錯誤的描述就是“My plugin Started…”,下面會有具體的演示效果。關於其它部分的程式碼現在可以暫時不用理會,後續的章節會進行詳細的說明。

現在先回到解壓llvm目錄的根目錄,首先來對這些原始碼生成一個Xcode工程,原始碼專案的編譯是由cmake管理(關於cmake詳細資料請參考:cmake官方教程),因此生成Xcode工程非常方便。執行下面的shell命令:

cd 解壓llvm目錄
mkdir build && cd build
cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64 -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON -DLIBCXX_INCLUDE_TESTS=OFF -DCOMPILER_RT_INCLUDE_TESTS=OFF -DCOMPILER_RT_ENABLE_IOS=OFF ../src

等待執行,提示成功後即可看到目錄下多了一個build目錄,點進去就可以看到一個Xcode的工程檔案。雙擊開啟專案,然後執行All_BUILD的scheme等待完成即可(這裡面會有一個compiler_rt的編譯報錯,表示無法編譯compiler_rt,由於這塊不涉及外掛編寫所以可以暫時忽略)。

接著選擇MyPlugin的scheme繼續buid,編譯成功後會在Debug/lib目錄中多出一個叫MyPlugin.dylib檔案,該檔案便是我們要在xcode中用的外掛。

PS:專案中可以看到剛才我們建立的cpp檔案
這裡寫圖片描述

3. 新增一個簡單的外掛專案到自己的專案

建立一個自己的工程專案,這裡我用的是testPlugin
開啟要使用外掛的Xcode專案,在build settings一欄中對Other C Flags一項進行編輯,調整為:

-Xclang -load -Xclang /llvm/build/Debug/lib/MyPlugin.dylib -Xclang -add-plugin -Xclang MyPlugin

注:最後一項-Xclang MyPlugin中的MyPlugin為外掛名字,一定要是自己設定的外掛名稱,否則無法呼叫外掛。
由於Clang外掛需要對應的Clang版本來載入,如果版本不一致會導致編譯錯誤,如下圖所示:
這裡寫圖片描述

不一致的Clang版本錯誤

為了解決這個問題需要調整Xcode中使用的Clang編譯器,將預設的編譯器改為我們自己編譯出來的編譯器。具體的方法是在build settings中再新增兩項自定義項(Editor-> Add build settings -> Add User-Defined Setting):

CC = / [解壓llvm目錄]/build/Debug/bin/clang
CXX =/ [解壓llvm目錄]/build/Debug/bin/clang++

目的用於指定Xcode的編譯器從之前預設的,改為自定義的Clang編譯器(注:CC和CXX中需要指定為你編譯出來的Clang所在的絕對路徑)。
Common + B 編譯則可以看到一個外掛輸出的錯誤提示。
這裡寫圖片描述

Good job!!
該錯誤正是我們D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
“My plugin Started..."));
輸出的錯誤。
至此我們成功的在自己的專案中使用了自己編譯的clang外掛。

摘錄:
https://my.oschina.net/vimfung/blog/866109
https://github.com/LiuShulong/SLClangTutorial/blob/master/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E7%BC%96%E5%86%99clang%E6%8F%92%E4%BB%B6%E5%92%8Clibtool.md
http://kangwang1988.github.io/tech/2016/10/31/check-code-style-using-clang-plugin.html