編寫調(diào)試Pass

LLVM Pass構(gòu)成了LLVM編譯器的轉(zhuǎn)換和優(yōu)化部分馆蠕,構(gòu)建這些轉(zhuǎn)換使用的分析結(jié)果兽赁,是編譯器的結(jié)構(gòu)化技術(shù)承桥。

All LLVM passes are subclasses of the Pass class, which implement functionality by overriding virtual methods inherited from Pass. Depending on how your pass works, you should inherit from the ModulePass , CallGraphSCCPass, FunctionPass , or LoopPass, or RegionPass classes

LLVM Pass框架的主要功能是,以高效的方式根據(jù)自己的約束執(zhí)行調(diào)度坷檩。


編寫Pass

首先农猬,配置和構(gòu)建LLVM赡艰。 您需要在LLVM源庫中的某個位置創(chuàng)建一個新目錄。 假設(shè)您制作的是lib / Transforms / Hello斤葱。 必須設(shè)置一個構(gòu)建腳本慷垮,該腳本將為新遍編譯源代碼。 創(chuàng)建Hello.h 以及.cpp揍堕,將以下內(nèi)容復(fù)制到CMakeLists.txt:

add_llvm_library( LLVMHello MODULE
  Hello.cpp

  PLUGIN_TOOL
  opt
  )

然后在lib/Transforms/CMakeLists.txt 中添加:

add_subdirectory(Hello)

此構(gòu)建腳本指定要編譯當(dāng)前目錄中的Hello.cpp文件并將其鏈接到共享對象$(LEVEL)/lib/LLVMHello.so中料身,該文件可以由opt工具通過其-load選項動態(tài)加載。

.cpp中實現(xiàn)代碼如下:

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"

using namespace llvm;

namespace {
  //匿名空間衩茸。與C(在全局范圍內(nèi))的“static”關(guān)鍵字相同芹血。 它使在匿名空間內(nèi)部聲明的內(nèi)容僅對當(dāng)前文件可見。
struct Hello : public FunctionPass {
  static char ID;   
  //聲明了LLVM用于標(biāo)識通過的通過標(biāo)識符楞慈。 使LLVM避免使用昂貴的C++運行時信息幔烛。
  Hello() : FunctionPass(ID) {}

  //重寫FunctionPass中的抽象虛函數(shù)
  bool runOnFunction(Function &F) override {
    errs() << "Hello: ";
    errs().write_escaped(F.getName()) << '\n';
    return false;
  }
}; // end of struct Hello
}  // end of anonymous namespace

char Hello::ID = 0;
//初始化pass ID
static RegisterPass<Hello> X("hello", "Hello World Pass",
                             false /* Only looks at CFG */,
                             false /* Analysis Pass */);
//注冊??當(dāng)前類,給定一個命令行參數(shù)“hello”以及一個名字“Hello World Pass”囊蓝。最后兩個參數(shù)描述了它的行為:如果pass執(zhí)行CFG而未修改它饿悬,則第三個參數(shù)設(shè)置為true; 如果pass是分析pass聚霜,例如控制樹pass狡恬,則將true作為第四個參數(shù)。

static RegisterStandardPasses Y(
    PassManagerBuilder::EP_EarlyAsPossible,
    [](const PassManagerBuilder &Builder,
       legacy::PassManagerBase &PM) { PM.add(new Hello()); });
//如果我們要將pass注冊為現(xiàn)有管道的一步俯萎,則可以使用提供的一些擴展傲宜,例如 PassManagerBuilder :: EP_EarlyAsPossible可以在任何優(yōu)化之前應(yīng)用我們的pass运杭,或者PassManagerBuilder :: EP_FullLinkTimeOptimizationLast在鏈接時間優(yōu)化后應(yīng)用我們的pass夫啊。

至此,從構(gòu)建目錄的頂層使用簡單的“ gmake”命令編譯文件辆憔,您會獲得一個新文件“ lib / LLVMHello.so”撇眯。 注意报嵌,此文件中的所有內(nèi)容都包含在一個匿名名稱空間中,pass是自包含的單元熊榛,不需要外部接口才有用锚国。

實際上,內(nèi)部的Pass并不是強連接的玄坦,他們經(jīng)由PassManager持有相互之間的依賴關(guān)系并且在執(zhí)行過程中解析血筑。下圖展示了每個Pass是如何被link到每個指定庫中的指定文件。

relationship.png


Pass classes and requirements

事實上煎楣,將C代碼轉(zhuǎn)換為IR的過程從詞法分析開始豺总,C代碼會被分解為一個令牌流,每個令牌表示一個標(biāo)識符择懂、文字喻喳、操作符,等等困曙。這個標(biāo)記流被提供給解析器表伦,解析器在Context free grammar (CFG)的幫助下為語言構(gòu)建一個抽象語法樹。然后進行語義分析慷丽,檢查代碼的語義是否正確蹦哼,然后生成IR代碼。

AST在語義分析中被大量使用盈魁,在語義分析中翔怎,編譯器檢查程序元素和語言的正確用法。在語義分析過程中杨耙,編譯器還根據(jù)AST生成符號表赤套。完整的樹遍歷可以驗證程序的正確性。在驗證正確性之后珊膜,AST作為代碼生成的基礎(chǔ)容握。

AST作為用于存儲關(guān)于lexer給出的令牌的各種信息的數(shù)據(jù)結(jié)構(gòu)。此信息在解析器邏輯中生成车柠,并且根據(jù)所解析的令牌類型填充ASTs剔氏。

The LLVM bit code file format (also known as bytecode) is actually two things: a bitstream container format and an encoding of LLVM IR into the container format.

為pass選擇父類時,應(yīng)盡可能選擇最具體的類竹祷,同時仍能滿足列出的要求谈跛。 這為LLVM Pass基礎(chǔ)結(jié)構(gòu)提供了優(yōu)化運行方式所必需的信息,從而使生成的編譯器不會不必要地變慢塑陵。

The ImmutablePass class

最簡單感憾,最無聊的Pass類型是“ ImmutablePass”類。 此pass類型用于不必運行令花,不更改狀態(tài)并且永遠(yuǎn)不需要更新阻桅。 這不是常規(guī)的轉(zhuǎn)換或分析類型凉倚,但可以提供有關(guān)當(dāng)前編譯器配置的信息。

The ModulePass class

ModulePass是你可以使用的所有超類中最通用的類嫂沉。 從ModulePass派生表示您的遍歷將整個程序作為一個單元使用稽寒,以不可預(yù)測的順序引用函數(shù)體,或者添加函數(shù)趟章,刪除函數(shù)杏糙。 由于無法知道ModulePass子類的行為,因此無法對其執(zhí)行進行優(yōu)化蚓土。

正確使用該類需要重寫:

virtual bool runOnModule(Module &M) = 0;

如果通過轉(zhuǎn)換修改了模塊搔啊,則應(yīng)返回true,否則返回false北戏。

The CallGraphSCCPass class

CallGraphSCCPass供需要在調(diào)用圖上自下而上遍歷程序的遍歷使用(被調(diào)用者在調(diào)用者之前)负芋。 從CallGraphSCCPass派生提供了一些構(gòu)建和遍歷CallGraph的機制,但是還允許系統(tǒng)優(yōu)化CallGraphSCCPasses的執(zhí)行嗜愈。 如果您的pass滿足以下概述的要求旧蛾,并且不滿足FunctionPass的要求,則應(yīng)從CallGraphSCCPass派生蠕嫁。

  1. 除當(dāng)前SCC以及SCC的直接調(diào)用方和直接被調(diào)用方中的功能外锨天,不允許檢查或修改其他任何Function。
  2. 需要保留當(dāng)前的CallGraph對象剃毒,并對其進行更新以反映對該程序所做的任何更改病袄。
  3. 不允許在當(dāng)前Module中添加或刪除SCC,盡管它們可能會更改SCC的內(nèi)容赘阀。
  4. 允許在當(dāng)前模塊中添加或刪除全局變量益缠。
  5. 允許在runOnSCC調(diào)用之間維護狀態(tài)(包括全局?jǐn)?shù)據(jù))。
virtual bool doInitialization(CallGraph &CG);

允許doInitialization方法執(zhí)行大部分CallGraphSCCPasses不允許執(zhí)行的操作基公。 可以添加和刪除函數(shù)幅慌,獲取函數(shù)的指針等。doInitialization方法旨在執(zhí)行簡單的初始化類型的工作轰豆,而這些工作不依賴于正在處理的SCC胰伍。 doInitialization方法調(diào)用不會與任何其他pass執(zhí)行重疊(因此,它應(yīng)該非乘嵝荩快)骂租。

virtual bool doFinalization(CallGraph &CG);

doFinalization方法是一種不常用的方法,當(dāng)通過框架完成對正在編譯的程序中的每個SCC調(diào)用runOnSCC時斑司,將調(diào)用該方法渗饮。

The FunctionPass class

與ModulePass子類相比,F(xiàn)unctionPass子類具有系統(tǒng)可以預(yù)期的可預(yù)測的局部行為。 所有FunctionPass在程序中的每個功能上執(zhí)行抽米,獨立于程序中的所有其他功能。 FunctionPasses不需要以特定順序執(zhí)行糙置,并且FunctionPasses不會修改外部函數(shù)云茸。

明確地說,F(xiàn)unctionPass子類不允許:

  1. 檢查或修改當(dāng)前正在處理的功能以外的其他功能谤饭。
  2. 從當(dāng)前Module添加或移除Function标捺。
  3. 從當(dāng)前Module添加或移除global variables。
  4. 維護runOnFunction調(diào)用之間的狀態(tài)(包括全局?jǐn)?shù)據(jù))揉抵。

通常亡容,實現(xiàn)FunctionPass很簡單。 FunctionPasses可能會重載三種虛擬方法來完成其工作冤今。 如果所有這些方法修改了程序闺兢,則應(yīng)返回true;否則返回false戏罢。

virtual bool doInitialization(Module &M);

可以使用doInitialization方法執(zhí)行FunctionPasses不允許執(zhí)行的大多數(shù)操作屋谭。 他們可以添加和刪除函數(shù),獲取指向函數(shù)的指針等龟糕。doInitialization方法旨在執(zhí)行簡單的初始化類型的工作桐磁,該類型不依賴于正在處理的函數(shù)。 doInitialization方法調(diào)用不會與任何其他pass執(zhí)行重疊(因此讲岁,它應(yīng)該非澄依蓿快)。

LowerAllocations 是如何使用此方法的一個很好的例子缓艳。 此過程將malloc和free指令轉(zhuǎn)換為依賴于平臺的malloc()和free()函數(shù)調(diào)用校摩。 它使用doInitialization方法獲取對其所需的malloc和free函數(shù)的引用,并在必要時向該模塊添加原型阶淘。

virtual bool runOnFunction(Function &F) = 0;
virtual bool doFinalization(Module &M);


Running a pass with opt

當(dāng)我們使用了RegisterPass方法注冊了pass之后秧耗,可以用opt命令執(zhí)行LLVM中的pass。

$ opt -load lib/LLVMHello.so -hello < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main

執(zhí)行opt -help指令可查看其他注冊的string:

$ opt -load lib/LLVMHello.so -help
OVERVIEW: llvm .bc -> .bc modular optimizer and analysis printer

USAGE: opt [subcommand] [options] <input bitcode file>

OPTIONS:
  Optimizations available:
...
    -guard-widening           - Widen guards
    -gvn                      - Global Value Numbering
    -gvn-hoist                - Early GVN Hoisting of Expressions
    -hello                    - Hello World Pass
    -indvars                  - Induction Variable Simplification
    -inferattrs               - Infer set function attributes

PassManager提供了一個可選指令(-time-passes)舶治,可以允許你獲取有關(guān)pass的執(zhí)行時間以及排隊的其他pass的信息:

$ opt -load lib/LLVMHello.so -hello -time-passes < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main
===-------------------------------------------------------------------------===
                      ... Pass execution timing report ...
===-------------------------------------------------------------------------===
  Total Execution Time: 0.0007 seconds (0.0005 wall clock)

   ---User Time---   --User+System--   ---Wall Time---  --- Name ---
   0.0004 ( 55.3%)   0.0004 ( 55.3%)   0.0004 ( 75.7%)  Bitcode Writer
   0.0003 ( 44.7%)   0.0003 ( 44.7%)   0.0001 ( 13.6%)  Hello World Pass
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0001 ( 10.7%)  Module Verifier
   0.0007 (100.0%)   0.0007 (100.0%)   0.0005 (100.0%)  Total


調(diào)試Pass

我們可以在命令行執(zhí)行bin目錄下的Mach-O分井,配置參數(shù)即可輸出Pass模塊中的日志:

clang XXX/CryptoUtils.cpp -mllvm -enable-symobf
//或者使用Opt直接輸出
opt -instcount input.bc -S -o output.ll

但是這種方式在開發(fā)階段用于調(diào)試確實太低效了。如何做到像開發(fā)自己項目一樣可以斷點調(diào)試呢霉猛?

可以采用如下步驟:

  1. 首先把工程編譯成Xcode項目:
> git clone --recursive -b release_80 https://github.com/HikariObfuscator/Hikari.git Hikari   
> mkdir buildXcode  
> cmake -G Xcode ../Hikari  

以官方Demo為例尺锚,代碼在lib/Transforms/Hello中。其通過RegisterPass注冊了Pass惜浅,所以可以通過opt -load去加載動態(tài)庫并指定參數(shù)hello瘫辩。

假設(shè)我們的源文件是test.c:

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

int main(){
    printf("%d",add(3,4));
    return 0;
}
  1. 首先需要編譯源文件生成bitcode:
  2. potency is defined as a measure of how difficult is to
clang -emit-llvm -c test.c -o test.bc
// or
clang -emit-llvm -S test.c -o test.ll
  1. 在 Xcode 中切換到 opt 的 Scheme,添加配置:


    Scheme.png
  2. 執(zhí)行opt

最后附上近期經(jīng)常使用的一些好用的命令:

    //匯編反匯編相互轉(zhuǎn)換
    - llvm?as example.ll ?o example.bc
    - llvm?dis example.bc ?o example.ll
    - clang -emit-llvm -S test.c -o test.ll     c to ll
    - clang -o test.c test.bc                               c to bc
    - clang -emit-llvm -o foo.bc -c foo.c       bc to c
    - lli test.ll                                                       執(zhí)行l(wèi)l代碼
    - llc -filetype=obj test.ll                         ir生成.o
    - gcc test.o                                                        .o生成.out
    - clang -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk -emit-llvm -S ocFile.m -o ocIR.ll    將OC類轉(zhuǎn)ll
    - swiftc –dump-ast main.swift                           Swift Abstract Syntax Tree (AST)
    - swiftc –emit-sil main.swift -o main.sil       Swift Intermediate Language (SIL)
    - swiftc –emit-ir main.swift -o main.ll         LLVM Intermediate Representation (LLVM IR)
    - swiftc –emit-assembly main.swift                  Assembly Language
    - swiftc main.swift -o hello                                swift to mach-O
    - swiftc -emit-bc main.swift -o hello.bc        swift to bc


工具鏈命令

  • llvm-as - 匯編器,將 .ll 匯編成字節(jié)碼伐厌。
  • llvm-dis - 反匯編器承绸,將字節(jié)碼編成可讀的 .ll 文件。
  • opt - 字節(jié)碼優(yōu)化器挣轨。
  • llc - 靜態(tài)編譯器军熏,將字節(jié)碼編譯成匯編代碼。
  • lli - 直接執(zhí)行 LLVM 字節(jié)碼卷扮。
  • llvm-link - 字節(jié)碼鏈接器荡澎,可以把多個字節(jié)碼文件鏈接成一個。
  • llvm-ar - 字節(jié)碼文件打包器晤锹。
  • llvm-lib - LLVM lib.exe 兼容庫工具摩幔。
  • llvm-nm - 列出字節(jié)碼和符號表。
  • llvm-config - 打印 LLVM 編譯選項鞭铆。
  • llvm-diff - 對兩個進行比較或衡。
  • llvm-cov - 輸出 coverage infomation。
  • llvm-profdata - Profile 數(shù)據(jù)工具车遂。
  • llvm-stress - 生成隨機 .ll 文件薇宠。
  • llvm-symbolizer - 地址對應(yīng)源碼位置,定位錯誤艰额。
  • llvm-dwarfdump - 打印 DWARF澄港。

調(diào)試工具

  • bugpoint - 自動測試案例工具
  • llvm-extract - 從一個 LLVM 的模塊里提取一個函數(shù)。
  • llvm-bcanalyzer - LLVM 字節(jié)碼分析器柄沮。

開發(fā)工具

  • FileCheck - 靈活的模式匹配文件驗證器回梧。
  • tblgen - C++ 代碼生成器。
  • lit - LLVM 集成測試器祖搓。
  • llvm-build - LLVM 構(gòu)建工程時需要的工具狱意。
  • llvm-readobj - LLVM Object 結(jié)構(gòu)查看器。

LLVM 源碼工程目錄介紹

  • llvm_examples_ - 使用 LLVM IR 和 JIT 的例子拯欧。
  • llvm_include_ - 導(dǎo)出的頭文件详囤。
  • llvm_lib_ - 主要源文件都在這里。
  • llvm_project_ - 創(chuàng)建自己基于 LLVM 的項目的目錄镐作。
  • llvm_test_ - 基于 LLVM 的回歸測試藏姐,健全檢察。
  • llvm_suite_ - 正確性该贾,性能和基準(zhǔn)測試套件羔杨。
  • llvm_tools_ - 基于 lib 構(gòu)建的可以執(zhí)行文件,用戶通過這些程序進行交互杨蛋,-help 可以查看各個工具詳細(xì)使用兜材。
  • llvm_utils_ - LLVM 源代碼的實用工具理澎,比如,查找 LLC 和 LLI 生成代碼差異工具曙寡, Vim 或 Emacs 的語法高亮工具等糠爬。

lib 目錄介紹

  • llvm_lib_IR/ - 核心類比如 Instruction 和 BasicBlock。
  • llvm_lib_AsmParser/ - 匯編語言解析器举庶。
  • llvm_lib_Bitcode/ - 讀取和寫入字節(jié)碼
  • llvm_lib_Analysis/ - 各種對程序的分析执隧,比如 Call Graphs,Induction Variables灯变,Natural Loop Identification 等等。
  • llvm_lib_Transforms/ - IR-to-IR 程序的變換捅膘。
  • llvm_lib_Target/ - 對像 X86 這樣機器的描述添祸。
  • llvm_lib_CodeGen/ - 主要是代碼生成,指令選擇器寻仗,指令調(diào)度和寄存器分配刃泌。
  • llvm_lib_ExecutionEngine/ - 在解釋執(zhí)行和JIT編譯場景能夠直接在運行時執(zhí)行字節(jié)碼的庫。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末署尤,一起剝皮案震驚了整個濱河市耙替,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌曹体,老刑警劉巖俗扇,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異箕别,居然都是意外死亡铜幽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門串稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來除抛,“玉大人,你說我怎么就攤上這事母截〉胶觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵清寇,是天一觀的道長喘漏。 經(jīng)常有香客問我,道長华烟,這世上最難降的妖魔是什么陷遮? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮垦江,結(jié)果婚禮上帽馋,老公的妹妹穿的比我還像新娘搅方。我一直安慰自己,他們只是感情好绽族,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布姨涡。 她就那樣靜靜地躺著,像睡著了一般吧慢。 火紅的嫁衣襯著肌膚如雪涛漂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天检诗,我揣著相機與錄音匈仗,去河邊找鬼。 笑死逢慌,一個胖子當(dāng)著我的面吹牛悠轩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播攻泼,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼火架,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忙菠?” 一聲冷哼從身側(cè)響起何鸡,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牛欢,沒想到半個月后骡男,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡傍睹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年洞翩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰望。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡骚亿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熊赖,到底是詐尸還是另有隱情来屠,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布震鹉,位于F島的核電站俱笛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏传趾。R本人自食惡果不足惜迎膜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浆兰。 院中可真熱鬧磕仅,春花似錦珊豹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劫恒,卻和暖如春贩幻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背两嘴。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工丛楚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憔辫。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓趣些,卻偏偏與公主長得像,于是被迫代替她去往敵國和親螺垢。 傳聞我的和親對象是個殘疾皇子喧务,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容