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到每個指定庫中的指定文件。
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)化運行方式所必需的信息,從而使生成的編譯器不會不必要地變慢塑陵。
最簡單感憾,最無聊的Pass類型是“ ImmutablePass”類。 此pass類型用于不必運行令花,不更改狀態(tài)并且永遠(yuǎn)不需要更新阻桅。 這不是常規(guī)的轉(zhuǎn)換或分析類型凉倚,但可以提供有關(guān)當(dāng)前編譯器配置的信息。
ModulePass是你可以使用的所有超類中最通用的類嫂沉。 從ModulePass派生表示您的遍歷將整個程序作為一個單元使用稽寒,以不可預(yù)測的順序引用函數(shù)體,或者添加函數(shù)趟章,刪除函數(shù)杏糙。 由于無法知道ModulePass子類的行為,因此無法對其執(zhí)行進行優(yōu)化蚓土。
正確使用該類需要重寫:
virtual bool runOnModule(Module &M) = 0;
如果通過轉(zhuǎn)換修改了模塊搔啊,則應(yīng)返回true,否則返回false北戏。
CallGraphSCCPass供需要在調(diào)用圖上自下而上遍歷程序的遍歷使用(被調(diào)用者在調(diào)用者之前)负芋。 從CallGraphSCCPass派生提供了一些構(gòu)建和遍歷CallGraph的機制,但是還允許系統(tǒng)優(yōu)化CallGraphSCCPasses的執(zhí)行嗜愈。 如果您的pass滿足以下概述的要求旧蛾,并且不滿足FunctionPass的要求,則應(yīng)從CallGraphSCCPass派生蠕嫁。
- 除當(dāng)前SCC以及SCC的直接調(diào)用方和直接被調(diào)用方中的功能外锨天,不允許檢查或修改其他任何Function。
- 需要保留當(dāng)前的CallGraph對象剃毒,并對其進行更新以反映對該程序所做的任何更改病袄。
- 不允許在當(dāng)前Module中添加或刪除SCC,盡管它們可能會更改SCC的內(nèi)容赘阀。
- 允許在當(dāng)前模塊中添加或刪除全局變量益缠。
- 允許在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)用該方法渗饮。
與ModulePass子類相比,F(xiàn)unctionPass子類具有系統(tǒng)可以預(yù)期的可預(yù)測的局部行為。 所有FunctionPass在程序中的每個功能上執(zhí)行抽米,獨立于程序中的所有其他功能。 FunctionPasses不需要以特定順序執(zhí)行糙置,并且FunctionPasses不會修改外部函數(shù)云茸。
明確地說,F(xiàn)unctionPass子類不允許:
- 檢查或修改當(dāng)前正在處理的功能以外的其他功能谤饭。
- 從當(dāng)前Module添加或移除Function标捺。
- 從當(dāng)前Module添加或移除global variables。
- 維護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)試呢霉猛?
可以采用如下步驟:
- 首先把工程編譯成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;
}
- 首先需要編譯源文件生成
bitcode
: - 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
-
在 Xcode 中切換到 opt 的 Scheme,添加配置:
Scheme.png 執(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é)碼的庫。