前言
編譯的主要任務(wù)是將源代碼文件作為輸入,最終輸出目標(biāo)文件,這期間發(fā)生了什么?便是我們本篇文章要介紹的。在開始之前我們先了解一下編譯器。
編譯器
編譯器(
compiler)是一種計(jì)算機(jī)程序,它會將某種編程語言寫成的源代碼(原始語言)轉(zhuǎn)換成另一種編程語言(目標(biāo)語言)。引自維基百科
傳統(tǒng)編譯器的架構(gòu),一般分三部分:
- 前端(
Frontend):解析源代碼,檢查源代碼是否有錯誤,并構(gòu)建特定語言的抽象語法樹(Abstract Syntax Tree縮寫:AST)來表示輸入的代碼。也負(fù)責(zé)選擇性的地將AST轉(zhuǎn)換為新的表示形式以進(jìn)行優(yōu)化。 - 優(yōu)化器(
Optimizer):負(fù)責(zé)進(jìn)行各種轉(zhuǎn)換,以嘗試改善代碼的運(yùn)行時間,例如消除冗余計(jì)算,并且通?;蚨嗷蛏俚鬲?dú)立于編程語言和目標(biāo)代碼。 - 后端(
Backend):也稱代碼生成器,將代碼映射到目標(biāo)架構(gòu)的指令集上;其常見部分有:指令選擇,寄存器分配,指定調(diào)度。
這種架構(gòu)的優(yōu)勢在于解耦合,實(shí)現(xiàn)一種編程語言,只需要實(shí)現(xiàn)它的前端,對于優(yōu)化器與后端部分是可以復(fù)用的;支持新的目標(biāo)架構(gòu),只需要實(shí)現(xiàn)它的后端即可;如果編譯器不是這種架構(gòu),三部分未分開,那么實(shí)現(xiàn)N個編程語言,去支持M個目標(biāo)架構(gòu),就需要實(shí)現(xiàn)N*M個編譯器。
這種傳統(tǒng)編譯器的架構(gòu)有三個成功的案例:
-
Java和.Net虛擬機(jī);它們都提供了對JIT編譯器和運(yùn)行時的支持,并且還定義了字節(jié)碼的格式(bytecode),這意味著任何可以編譯為字節(jié)碼的語言,都可以復(fù)用優(yōu)化器和JIT(動態(tài)編譯)和運(yùn)行時能力。 - 將輸入源轉(zhuǎn)換為
C代碼(或其他某種語言)并通過現(xiàn)有的C編譯器編譯 - 這種模式的最終成功實(shí)施是
GCC,GCC支持許多前端和后端,并擁有活躍而廣泛的貢獻(xiàn)者社區(qū)。
GCC
GCC的概述
Xcode5之前的版本中使用的是GCC編譯器,由于GCC,歷史悠久,體系結(jié)構(gòu)相對復(fù)雜,功能模塊化復(fù)用難度大且不受蘋果公司的約束,很難滿足蘋果系統(tǒng)的發(fā)展需求。因此在Xcode5中拋棄了GCC,采用Clang/LLVM進(jìn)行編譯。
GCC:是GNU Compiler Collection的縮寫,指GNU編譯器套裝。Linux系統(tǒng)的核心組成部分就有GNU工具鏈,GCC也是GNU工具鏈的重要組成部分,因此GCC也是作為Linux系統(tǒng)的標(biāo)準(zhǔn)編譯器。GCC可處理的語言有C、C++、Objective-C、Java、Go等。
使用GCC命令gcc -ccc-print-phases main.m查看編譯OC的步驟:
*deMacBook-Pro:Mach-O *$ gcc -ccc-print-phases main.m
+- 0: input, "main.m", objective-c
+- 1: preprocessor, {0}, objective-c-cpp-output
+- 2: compiler, {1}, ir
+- 3: backend, {2}, assembler
+- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
GCC的架構(gòu)
前端讀取源文件將其轉(zhuǎn)化為AST,由于每種語言生成的AST是有差異的,所以需要需要轉(zhuǎn)換為通用的與語言無關(guān)的統(tǒng)一形式GENERIC。
中端將GENERIC,利用gimplifier技術(shù),簡化GENERIC的復(fù)雜結(jié)構(gòu),將其轉(zhuǎn)換為一種中間表示形式稱為:GIMPLE,再轉(zhuǎn)換為另一種SSA(static single assignment)的表示形式也是用于優(yōu)化的,GCC對SSA樹執(zhí)行20多種不同的優(yōu)化。經(jīng)過SSA優(yōu)化后,該樹將轉(zhuǎn)換回GIMPLE形式,用來生成一個RTL樹,RTL寄存器轉(zhuǎn)換語言,全稱(register-transfer language);RTL是基于硬件的表示形式,與抽象的目標(biāo)架構(gòu)相對應(yīng),處理寄存器分配、指令調(diào)度等。RTL優(yōu)化過程以RTL形式對樹進(jìn)行優(yōu)化。
后端使用RTL表示形式生成目標(biāo)架構(gòu)的匯編代碼。如:x86后端。
LLVM
LLVM的概述
LLVM項(xiàng)目是模塊化和可重用的編譯器及工具鏈技術(shù)的集合。名稱LLVM是Low Level Virtual Machine的縮寫,盡管名稱如此,但是LLVM與傳統(tǒng)虛擬機(jī)關(guān)系不大,它是LLVM項(xiàng)目的全名。
The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Despite its name, LLVM has little to do with traditional virtual machines. The name "LLVM" itself is not an acronym; it is the full name of the project. 引自LLVM官網(wǎng)
LLVM有許多的子項(xiàng)目,比如Clang,LLDB,MLIR等。
LLVM的歷史
-
LLVM起源于2000年Vikram Adve與Chris Lattner的研究,目的:為所有的靜態(tài)語言(C/C++)和動態(tài)語言(運(yùn)行時改變其結(jié)構(gòu)的語言,如:OC/JavaScript)創(chuàng)造出動態(tài)編譯技術(shù)。 - 蘋果公司
2005雇傭Chris Lattner與他的團(tuán)隊(duì)為蘋果電腦開發(fā)應(yīng)用程序系統(tǒng),LLVM為現(xiàn)今macOS與iOS開發(fā)工具的一部分。 - 因
LLVM對產(chǎn)業(yè)的貢獻(xiàn),2012年獲得了ACM軟件系統(tǒng)獎。獲得該獎項(xiàng)的有Unix、Java、TCP/IP、DNS、Mach -
2019年10月開始,LLVM項(xiàng)目的代碼托管正式遷移到了GitHub。
LLVM的架構(gòu)
LLVM最重要的設(shè)計(jì)是中間表示LLVM Intermediate Representation(IR),它是在編譯器中表示代碼的一種形式。優(yōu)化器使用LLVM IR作中間的轉(zhuǎn)換與分析處理。LLVM IR本身就是具有良好語義定義的一流語言。
在基于LLVM的編譯器中,Frontend負(fù)責(zé)對輸入的代碼進(jìn)行解析,校驗(yàn)和分析錯誤,然后將解析后的代碼轉(zhuǎn)換為LLVM IR(通常情況,是將構(gòu)建的抽象語法樹AST轉(zhuǎn)換為LLVM IR,但不總是這樣的)。可以選擇通過一系列分析和優(yōu)化過程來傳遞LLVM IR,以改進(jìn)代碼,然后將其發(fā)送到代碼生成器(Backend)中,生成原始的機(jī)器碼。
LLVM IR不僅是完整的代碼表示,而且也是優(yōu)化器optimizer的唯一接口。這意味著寫一個LLVM的前端只需要知道LLVM IR即可,這是LLVM的一個新穎的特性,也是LLVM成功地被廣泛應(yīng)用的一個主要原因。反觀GCC編譯器,寫一個前端需要知道生成的GCC樹的數(shù)據(jù)結(jié)構(gòu)以及使用GIMPLE去寫GCC的前端,GCC后端需要知道RTL是如何工作的。
LLVM IR是前端輸出,后端的輸入:
LLVM廣義是指LLVM整個架構(gòu),狹義指Clang編譯器的后端。
Clang
Clang是LLVM的子項(xiàng)目,是C、C++、Objective C語言的編譯器的前端。Clang編譯Objective-C代碼時速度為GCC的3倍。詳見維基百科。
Clang編譯過程
下面是一個基于簡單的OC工程,不依賴Xcode,而是使用終端編譯的例子。
編譯前工程源代碼主要分為main.m和Person.m類,代碼如下:
///main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#define SomeDefine @"你好,世界"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 注釋
NSLog(@"Hello, World!");
#pragma mark 我是注釋
NSLog(@"%@",SomeDefine);
/// MARK: 我也是注釋
Person *instance = [[Person alloc]init];
[instance share];
}
return 0;
}
///Person.m
#import "Person.h"
@implementation Person
- (void)share {
NSLog(@"持之以恒");
}
@end
首先我們運(yùn)行clang -ccc-print-phases main.m查看整體的編譯過程:
*deMacBook-Pro:Mach-O *$ clang -ccc-print-phases main.m
+- 0: input, "main.m", objective-c
+- 1: preprocessor, {0}, objective-c-cpp-output
+- 2: compiler, {1}, ir
+- 3: backend, {2}, assembler
+- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
接下來,基于這個例子,我們使用終端逐步編譯,生成我們的可執(zhí)行文件,并最終控制臺打印我們的信息。
預(yù)處理
基于輸入,通過預(yù)處理器執(zhí)行一系列的文本轉(zhuǎn)換與文本處理。預(yù)處理器是在真正的編譯開始之前由編譯器調(diào)用的獨(dú)立程序。
終端命令:
# 編譯階段選擇參數(shù): -E 運(yùn)行預(yù)處理這一步
clang -E main.m
# 預(yù)處理結(jié)果輸出到main.mi文件中
clang -E main.m -o main.mi
輸出結(jié)果:
# 193 "/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 9 "main.m" 2
# 1 "./Person.h" 1
# 10 "./Person.h"
#pragma clang assume_nonnull begin
@interface Person : NSObject
- (void)share;
@end
#pragma clang assume_nonnull end
# 10 "main.m" 2
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
NSLog(@"%@",@"你好,世界");
Person *instance = [[Person alloc]init];
[instance share];
}
return 0;
}
最終C輸出.i文件,C++輸出.ii文件,Objective-C輸出.mi文件,Objective-C ++輸出.mii文件。
預(yù)處理的任務(wù):
將輸入文件讀到內(nèi)存,并斷行;
替換注釋為單個空格;
Tokenization將輸入轉(zhuǎn)換為一系列預(yù)處理Tokens;處理
#import、#include將所引的庫,以遞歸的方式,插入到#import或#include所在的位置;替換宏定義;
條件編譯,根據(jù)條件包括或排除程序代碼的某些部分;
插入行標(biāo)記;
在預(yù)處理的輸出中,源文件名和行號信息會以# linenum filename flags形式傳遞,這被稱為行標(biāo)記,代表著接下來的內(nèi)容開始于源文件filename的第linenum行,而flags則會有0或者多個,有1、2、3、4;如果有多個flags時,彼此使用分號隔開。詳見此處。
每個標(biāo)識的表示內(nèi)容如下:
-
1表示一個新文件的開始 -
2表示返回文件(包含另一個文件后) -
3表示以下文本來自系統(tǒng)頭文件,因此應(yīng)禁止某些警告 -
4表示應(yīng)將以下文本視為包裝在隱式extern "C"塊中。
比如# 10 "main.m" 2,表示導(dǎo)入Person.h文件后回到main.m文件的第10行。
詞法分析
詞法分析屬于預(yù)處理部分,詞法分析的整個過程,主要是按照:標(biāo)識符、 數(shù)字、字符串文字、 標(biāo)點(diǎn)符號,將我們的代碼分割成許多字符串序列,其中每個元素我們稱之為Token,整個過程稱為Tokenization。
終端輸入:
# -fmodules: Enable the 'modules' language feature
# -fsyntax-only, Run the preprocessor, parser and type checking stages
#-Xclang <arg>: Pass <arg> to the clang compiler
# -dump-tokens: Run preprocessor, dump internal rep of tokens
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
-fmodules:啟用“模塊”語言功能。關(guān)于Modules特性,詳見此處,大意為使用import代替include,編譯速度快。
-fsyntax-only:運(yùn)行預(yù)處理器,解析器和類型檢查階段。
-Xclang <arg>:傳遞參數(shù)到clang的編譯器。
dump-tokens:運(yùn)行預(yù)處理器,轉(zhuǎn)儲Token的內(nèi)部表示。
更多關(guān)于Clang參數(shù)的描述,請前往此處。
輸出結(jié)果:
....
int 'int' [StartOfLine] Loc=<main.m:11:1>
identifier 'main' [LeadingSpace] Loc=<main.m:11:5>
l_paren '(' Loc=<main.m:11:9>
int 'int' Loc=<main.m:11:10>
identifier 'argc' [LeadingSpace] Loc=<main.m:11:14>
comma ',' Loc=<main.m:11:18>
const 'const' [LeadingSpace] Loc=<main.m:11:20>
char 'char' [LeadingSpace] Loc=<main.m:11:26>
star '*' [LeadingSpace] Loc=<main.m:11:31>
identifier 'argv' [LeadingSpace] Loc=<main.m:11:33>
l_square '[' Loc=<main.m:11:37>
r_square ']' Loc=<main.m:11:38>
r_paren ')' Loc=<main.m:11:39>
l_brace '{' [LeadingSpace] Loc=<main.m:11:41>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:12:5>
identifier 'autoreleasepool' Loc=<main.m:12:6>
l_brace '{' [LeadingSpace] Loc=<main.m:12:22>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main.m:14:9>
l_paren '(' Loc=<main.m:14:14>
at '@' Loc=<main.m:14:15>
string_literal '"Hello, World!"' Loc=<main.m:14:16>
r_paren ')' Loc=<main.m:14:31>
semi ';' Loc=<main.m:14:32>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main.m:16:9>
l_paren '(' Loc=<main.m:16:14>
at '@' Loc=<main.m:16:15>
string_literal '"%@"' Loc=<main.m:16:16>
comma ',' Loc=<main.m:16:20>
at '@' Loc=<main.m:16:21 <Spelling=main.m:10:20>>
string_literal '"你好,世界"' Loc=<main.m:16:21 <Spelling=main.m:10:21>>
r_paren ')' Loc=<main.m:16:31>
semi ';' Loc=<main.m:16:32>
identifier 'Person' [StartOfLine] [LeadingSpace] Loc=<main.m:18:9>
star '*' [LeadingSpace] Loc=<main.m:18:16>
identifier 'instance' Loc=<main.m:18:17>
equal '=' [LeadingSpace] Loc=<main.m:18:26>
l_square '[' [LeadingSpace] Loc=<main.m:18:28>
l_square '[' Loc=<main.m:18:29>
identifier 'Person' Loc=<main.m:18:30>
identifier 'alloc' [LeadingSpace] Loc=<main.m:18:37>
r_square ']' Loc=<main.m:18:42>
identifier 'init' Loc=<main.m:18:43>
r_square ']' Loc=<main.m:18:47>
semi ';' Loc=<main.m:18:48>
l_square '[' [StartOfLine] [LeadingSpace] Loc=<main.m:19:9>
identifier 'instance' Loc=<main.m:19:10>
identifier 'share' [LeadingSpace] Loc=<main.m:19:19>
r_square ']' Loc=<main.m:19:24>
semi ';' Loc=<main.m:19:25>
r_brace '}' [StartOfLine] [LeadingSpace] Loc=<main.m:20:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:21:5>
numeric_constant '0' [LeadingSpace] Loc=<main.m:21:12>
semi ';' Loc=<main.m:21:13>
r_brace '}' [StartOfLine] Loc=<main.m:22:1>
eof '' Loc=<main.m:22:2>
詞法分析中Token包含信息(詳請見此處):
Sourece Location:表示Token開始的位置,比如:Loc=<main.m:11:5>;Token Kind:表示Token的類型,比如:identifier、numeric_constant、string_literal;Flags:詞法分析器和處理器跟蹤每個Token的基礎(chǔ),目前有四個Flag分別是:
StartOfLine:表示這是每行開始的第一個Token;LeadingSpace:當(dāng)通過宏擴(kuò)展Token時,在Token之前有一個空格字符。該標(biāo)志的定義是依據(jù)預(yù)處理器的字符串化要求而進(jìn)行的非常嚴(yán)格地定義。DisableExpand:該標(biāo)志在預(yù)處理器內(nèi)部使用,用來表示identifier令牌禁用宏擴(kuò)展。NeedsCleaning:如果令牌的原始拼寫包含三字符組或轉(zhuǎn)義的換行符,則設(shè)置此標(biāo)志。
語法分析(Parsing)與語義分析
此階段對輸入文件進(jìn)行語法分析,將預(yù)處理器生成的Tokens轉(zhuǎn)換為語法分析樹;一旦生成語法分析樹后,將會進(jìn)行語義分析,執(zhí)行類型檢查和代碼格式檢查。這個階段負(fù)責(zé)生成大多數(shù)編譯器警告以及語法分析過程的錯誤。最終輸出AST(抽象語法樹)。
Parser的意義與作用
所謂 parser,一般是指把某種格式的文本(字符串)轉(zhuǎn)換成某種數(shù)據(jù)結(jié)構(gòu)的過程。最常見的 parser,是把程序文本轉(zhuǎn)換成編譯器內(nèi)部的一種叫做“抽象語法樹”(AST)的數(shù)據(jù)結(jié)構(gòu)。摘自對 Parser 的誤解-王垠
AST的示意圖(來源):
終端輸入:
# -fmodules: Enable the 'modules' language feature
# -fsyntax-only, Run the preprocessor, parser and type checking stages
#-Xclang <arg>: Pass <arg> to the clang compiler
# -ast-dump: Build ASTs and then debug dump them
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
輸出結(jié)果:
TranslationUnitDecl 0x7f80ea01c408 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7f80ea01cca0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7f80ea01c9a0 '__int128'
#...
# cutting out internal declarations of clang
#...
|-ImportDecl 0x7f80ea27d9d8 <main.m:8:1> col:1 implicit Foundation
|-ImportDecl 0x7f80ea27da18 <./Person.h:8:1> col:1 implicit Foundation
|-ObjCInterfaceDecl 0x7f80ea294ff8 <line:12:1, line:14:2> line:12:12 Person
| |-super ObjCInterface 0x7f80ea27db18 'NSObject'
| `-ObjCMethodDecl 0x7f80ea2951f0 <line:13:1, col:14> col:1 - share 'void'
`-FunctionDecl 0x7f80ea295620 <main.m:11:1, line:22:1> line:11:5 main 'int (int, const char **)'
|-ParmVarDecl 0x7f80ea2953b0 <col:10, col:14> col:14 argc 'int'
|-ParmVarDecl 0x7f80ea2954d0 <col:20, col:38> col:33 argv 'const char **':'const char **'
`-CompoundStmt 0x7f80ea29e5b8 <col:41, line:22:1>
|-ObjCAutoreleasePoolStmt 0x7f80ea29e570 <line:12:5, line:20:5>
| `-CompoundStmt 0x7f80ea29e540 <line:12:22, line:20:5>
| |-CallExpr 0x7f80ea2a26f0 <line:14:9, col:31> 'void'
| | |-ImplicitCastExpr 0x7f80ea2a26d8 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
| | | `-DeclRefExpr 0x7f80ea2a25e0 <col:9> 'void (id, ...)' Function 0x7f80ea295760 'NSLog' 'void (id, ...)'
| | `-ImplicitCastExpr 0x7f80ea2a2718 <col:15, col:16> 'id':'id' <BitCast>
| | `-ObjCStringLiteral 0x7f80ea2a2660 <col:15, col:16> 'NSString *'
| | `-StringLiteral 0x7f80ea2a2638 <col:16> 'char [14]' lvalue "Hello, World!"
| |-CallExpr 0x7f80ea298298 <line:16:9, col:31> 'void'
| | |-ImplicitCastExpr 0x7f80ea298280 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
| | | `-DeclRefExpr 0x7f80ea2a2730 <col:9> 'void (id, ...)' Function 0x7f80ea295760 'NSLog' 'void (id, ...)'
| | |-ImplicitCastExpr 0x7f80ea2982c8 <col:15, col:16> 'id':'id' <BitCast>
| | | `-ObjCStringLiteral 0x7f80ea2a27a8 <col:15, col:16> 'NSString *'
| | | `-StringLiteral 0x7f80ea2a2788 <col:16> 'char [3]' lvalue "%@"
| | `-ObjCStringLiteral 0x7f80ea298260 <line:10:20, col:21> 'NSString *'
| | `-StringLiteral 0x7f80ea298238 <col:21> 'char [16]' lvalue "\344\275\240\345\245\275\357\274\214\344\270\226\347\225\214"
| |-DeclStmt 0x7f80ea29e4a8 <line:18:9, col:48>
| | `-VarDecl 0x7f80ea298320 <col:9, col:47> col:17 used instance 'Person *' cinit
| | |-ObjCMessageExpr 0x7f80ea2988d0 <col:28, col:47> 'Person *' selector=init
| | | `-ObjCMessageExpr 0x7f80ea298658 <col:29, col:42> 'Person *' selector=alloc class='Person'
| | `-FullComment 0x7f80ea2a3900 <line:17:12, col:33>
| | `-ParagraphComment 0x7f80ea2a38d0 <col:12, col:33>
| | `-TextComment 0x7f80ea2a38a0 <col:12, col:33> Text=" MARK: 我也是注釋"
| `-ObjCMessageExpr 0x7f80ea29e510 <line:19:9, col:24> 'void' selector=share
| `-ImplicitCastExpr 0x7f80ea29e4f8 <col:10> 'Person *' <LValueToRValue>
| `-DeclRefExpr 0x7f80ea29e4c0 <col:10> 'Person *' lvalue Var 0x7f80ea298320 'instance' 'Person *'
`-ReturnStmt 0x7f80ea29e5a8 <line:21:5, col:12>
`-IntegerLiteral 0x7f80ea29e588 <col:12> 'int' 0
Clang的AST是從TranslationUnitDecl節(jié)點(diǎn)開始進(jìn)行遞歸遍歷的;AST中許多重要的Node,繼承自Type、Decl、DeclContext、Stmt。
-
Type :表示類型,比如
BuiltinType -
Decl :表示一個聲明
declaration或者一個定義definition,比如:變量,函數(shù),結(jié)構(gòu)體,typedef; -
DeclContext :用來聲明表示上下文的特定
decl類型的基類; -
Stmt :表示一條陳述
statement; -
Expr:在
Clang的語法樹中也表示一條陳述statements;
代碼優(yōu)化和生成
這個階段主要任務(wù)是將AST轉(zhuǎn)換為底層中間的代碼LLVM IR,并且最終生成機(jī)器碼;期間負(fù)責(zé)生成目標(biāo)架構(gòu)的代碼以及優(yōu)化生成的代碼。最終輸出.s文件(匯編文件)。
LLVM IR有三種格式:
- 文本格式:
.ll文件 - 內(nèi)存中用以優(yōu)化自身時,執(zhí)行檢查和修改的數(shù)據(jù)結(jié)構(gòu)(編譯過程中載入內(nèi)存的形式)
- 磁盤二進(jìn)制(
BitCode)格式:.bc文件
LLVM提供了.ll與.bc相互轉(zhuǎn)換的工具:
-
llvm-as:可將.ll轉(zhuǎn)為.bc -
llvm-dis:可將.bc轉(zhuǎn)為.ll
終端輸入:
# -S : Run LLVM generation and optimization stages and target-specific code generation,producing an assembly file
# -fobjc-arc : Synthesize retain and release calls for Objective-C pointers
# -emit-llvm : Use the LLVM representation for assembler and object files
# -o <file> : Write output to <file>
# 匯編表示成.ll文件 -fobjc-arc 可忽略,不作代碼優(yōu)化
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
# 目標(biāo)文件表示成 .bc 文件
# -c : Only run preprocess, compile, and assemble steps
clang -emit-llvm -c main.m -o main.bc
#.ll與.bc的相互轉(zhuǎn)換
llvm-as main.ll -o main.bc
llvm-dis main.bc -o main.ll
此處使用了參數(shù)-emit-llvm,來查看LLVM IR。
輸出結(jié)果:
# 此處只貼main函數(shù)部分
define i32 @main(i32 %0, i8** %1) #1 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca %0*, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%7 = call i8* @llvm.objc.autoreleasePoolPush() #2
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.2 to i8*), %1* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.4 to %1*))
%8 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%9 = bitcast %struct._class_t* %8 to i8*
%10 = call i8* @objc_alloc_init(i8* %9)
%11 = bitcast i8* %10 to %0*
store %0* %11, %0** %6, align 8
%12 = load %0*, %0** %6, align 8
%13 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !9
%14 = bitcast %0* %12 to i8*
call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*)*)(i8* %14, i8* %13)
%15 = bitcast %0** %6 to i8**
call void @llvm.objc.storeStrong(i8** %15, i8* null) #2
call void @llvm.objc.autoreleasePoolPop(i8* %7)
ret i32 0
}
代碼優(yōu)化
Clang代碼優(yōu)化參數(shù)有-O0、 -O1、 -O2、 -O3、 -Ofast、-Os、 -Oz 、-Og、 -O、-O4
-
-O0:表示沒有優(yōu)化;編譯速度最快并生成最可調(diào)試的代碼 -
-O1:優(yōu)化程度介于-O0~-O2之間。 -
-O2:適度的優(yōu)化水平,可實(shí)現(xiàn)最優(yōu)化 -
-O3:與-O2相似,不同之處在于它優(yōu)化的時間比較長,可能會生成更大的代碼 -
-O4:當(dāng)前等效于-O3 -
-Ofast:啟用-O3中的所有優(yōu)化并且可能啟用一些激進(jìn)優(yōu)化 -
-Os:與-O2一樣,具有額外的優(yōu)化功能以減少代碼大小 -
-Oz:類似于-Os,進(jìn)一步減小了代碼大小 -
-Og:類似-O1 -
-O:相當(dāng)于-O2
終端輸入:
clang -S -O2 -fobjc-arc -emit-llvm main.m -o main.ll
輸出結(jié)果:
#LLVM IR文件頭信息
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
#結(jié)構(gòu)體的定義
%0 = type opaque
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
# 全局變量、私有/外部/內(nèi)部常量的定義或聲明
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [14 x i8] c"Hello, World!\00", section "__TEXT,__cstring,cstring_literals", align 1
# --全局結(jié)構(gòu)體定義與初始化
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i64 13 }, section "__DATA,__cfstring", align 8 #0
@.str.1 = private unnamed_addr constant [3 x i8] c"%@\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_.2 = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.1, i32 0, i32 0), i64 2 }, section "__DATA,__cfstring", align 8 #0
@.str.3 = private unnamed_addr constant [6 x i16] [i16 20320, i16 22909, i16 -244, i16 19990, i16 30028, i16 0], section "__TEXT,__ustring", align 2
@_unnamed_cfstring_.4 = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 2000, i8* bitcast ([6 x i16]* @.str.3 to i8*), i64 5 }, section "__DATA,__cfstring", align 8 #0
@"OBJC_CLASS_$_Person" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = internal global %struct._class_t* @"OBJC_CLASS_$_Person", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"share\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = internal externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@llvm.compiler.used = appending global [3 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*)], section "llvm.metadata"
# main函數(shù)的入口:`dso_local`:main函數(shù)解析為統(tǒng)一鏈接單元的符號,而非外部替換的符號
; Function Attrs: ssp uwtable
define dso_local i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #1 {
%3 = tail call i8* @llvm.objc.autoreleasePoolPush() #2
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*)), !clang.arc.no_objc_arc_exceptions !8
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.2 to i8*), %0* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.4 to %0*)), !clang.arc.no_objc_arc_exceptions !8
%4 = load i8*, i8** bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8**), align 8
%5 = tail call i8* @objc_alloc_init(i8* %4), !clang.arc.no_objc_arc_exceptions !8
%6 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*)*)(i8* %5, i8* %6), !clang.arc.no_objc_arc_exceptions !8
tail call void @llvm.objc.release(i8* %5) #2, !clang.imprecise_release !8
tail call void @llvm.objc.autoreleasePoolPop(i8* %3) #2
ret i32 0
}
#函數(shù)聲明
; Function Attrs: nounwind
declare i8* @llvm.objc.autoreleasePoolPush() #2
declare void @NSLog(i8*, ...) local_unnamed_addr #3
declare i8* @objc_alloc_init(i8*) local_unnamed_addr
; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) local_unnamed_addr #4
; Function Attrs: nounwind
declare void @llvm.objc.release(i8*) #2
; Function Attrs: nounwind
declare void @llvm.objc.autoreleasePoolPop(i8*) #2
#屬性組
attributes #0 = { "objc_arc_inert" }
attributes #1 = { ssp uwtable "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }
attributes #3 = { "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #4 = { nonlazybind }
#該`module`的元數(shù)據(jù)
##命名元數(shù)據(jù)
!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
!llvm.ident = !{!7}
##未命名的元數(shù)據(jù)
!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 1, !"Objective-C Garbage Collection", i8 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"clang version 12.0.0"}
!8 = !{}
淺析 LLVM IR
Module:
LLVM程序是由Module組成的,每個Module是輸入程序的翻譯單元。每個Module都是由functions、global variables和symbol table entries組成。Module會通過LLVM鏈接器組合到一起,鏈接器會合并函數(shù)以及全局變量的定義,解決前置聲明以及合并符號表。Target Datalayout:
Module需要以字符串的形式指定特定于目標(biāo)架構(gòu)的數(shù)據(jù)布局方式,該字符串指定如何在內(nèi)存中布局?jǐn)?shù)據(jù)。如:target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"。-
元數(shù)據(jù):
LLVM IR允許元數(shù)據(jù)被附加到能夠傳遞代碼額外信息給優(yōu)化器和代碼生成器的程序指令上。所有元數(shù)據(jù)在語法上均由!標(biāo)識。元數(shù)據(jù)的兩個原語:元數(shù)據(jù)字符串和元數(shù)據(jù)節(jié)點(diǎn)。- 元數(shù)據(jù)字符串:用
""引起來的字符串,以!作為前綴。如:!"clang version 12.0.0" - 元數(shù)據(jù)節(jié)點(diǎn):用
{}括起來,使用,隔開多個元素,以!作為前綴。如:!{i32 7, !"PIC Level", i32 2}
- 元數(shù)據(jù)字符串:用
-
命名元數(shù)據(jù):是元數(shù)據(jù)節(jié)點(diǎn)的集合
; Some unnamed metadata nodes, which are referenced by the named metadata. !0 = !{!"zero"} !1 = !{!"one"} !2 = !{!"two"} ; A named metadata. !name = !{!0, !1, !2} -
Linkage Types:所有全局變量和函數(shù)都具有鏈接類型,如上述
IR中的:-
external:module外部可用 -
private:module內(nèi)部可用 -
appending:僅應(yīng)用于數(shù)組類型的全局變量的指針。當(dāng)兩個使用了appending的全局變量鏈接到一起的時候,這兩個全局的數(shù)組會被拼接到一起。 -
internal:與private相似,但該值在目標(biāo)文件中顯示為本地符號。與C語言中static關(guān)鍵字的概念相對應(yīng)。
-
屬性組:屬性組是
IR中的對象(函數(shù)、全局變量)引用的屬性組合。它們對于保持.ll文件的可讀性很重要,因?yàn)樵S多函數(shù)將使用相同的屬性集。如上述IR中的#0~#4。-
函數(shù)屬性:被用來傳遞一個函數(shù)附加信息。函數(shù)屬性被認(rèn)為是函數(shù)的一部分,而不是函數(shù)類型,所以不同的函數(shù)屬性可以有相同的函數(shù)類型。上述
IR中用到最多的函數(shù)屬性:-
nounwind:表示函數(shù)不會拋出異常 -
nonlazybind:阻止函數(shù)中某些符號的延遲綁定。
-
-
參數(shù)屬性:函數(shù)的返回類型以及每個參數(shù)都有與之關(guān)聯(lián)的參數(shù)屬性集合。被用來傳遞一個函數(shù)的返回值與參數(shù)的附加信息。參數(shù)屬性是函數(shù)的一部分,而不是函數(shù)類型,所以有不同參數(shù)屬性的函數(shù)可以有相同的參數(shù)類型。上述
IR中用到最多的參數(shù)屬性:-
nocapture:表示函數(shù)調(diào)用不會捕獲參數(shù)的指針,這個屬性對于返回值是無效的,僅適用于參數(shù)。 -
readnone:應(yīng)用于參數(shù)表示函數(shù)不會取消對此參數(shù)指針的引用。
-
-
-
@為全局標(biāo)識符。以其開頭標(biāo)識函數(shù),全局變量; -
%為本地標(biāo)識符。以其開頭標(biāo)識寄存器名稱,類型; - 標(biāo)識符的不同的格式:
- 命名值,表示為以上述標(biāo)識符為前綴的字符串,如:
%struct._ivar_t、@.str - 未命名值,表示為以上述標(biāo)識為前綴的無符號的數(shù)值,如:
%0、%1
- 命名值,表示為以上述標(biāo)識符為前綴的字符串,如:
-
-
#語法 %T1 = type { <type list> } ; Identified normal struct type %T2 = type <{ <type list> }> ; Identified packed struct type #表示結(jié)構(gòu)體的對齊方式為1字節(jié) #示例 {i32, i32} %mytype = type { %mytype*, i32 } -
#語法 [<# elements> x <elementtype>] #語義 `elements`是個`integer`的值;`elementtype`是任意有大小的類型 #示例 [40 x i32] Array of 40 32-bit integer values -
#語法 @<GlobalVarName> = [Linkage] [PreemptionSpecifier] [Visibility] [DLLStorageClass] [ThreadLocal] [(unnamed_addr|local_unnamed_addr)] [AddrSpace] [ExternallyInitialized] <global | constant> <Type> [<InitializerConstant>] [, section "name"] [, comdat [($name)]] [, align <Alignment>] (, !name !N)* #示例 @G = external global i32 #just declare @G = external global i32 8 #InitializerConstant-
global constant:表示該變量的內(nèi)容將永遠(yuǎn)不會被修改。 -
unnamed_addr:表示該變量的地址并不重要,僅指示內(nèi)容。 -
local_unnamed_addr:表示變量的地址在module內(nèi)并不重要。
-
-
Runtime Preemption Specifiers:運(yùn)行時搶占說明符。全局變量,函數(shù)和別名可以具有一個可選的運(yùn)行時搶占說明符。如果未明確指定搶占說明符,則假定該符號為
dso_preemptable。-
dso_preemptable:表示函數(shù)或者變量在運(yùn)行時會被外部的鏈接單元替換 -
dso_local:表示函數(shù)或變量將解析為同一鏈接單元中的符號。即使定義不在此編譯單元內(nèi),也將生成直接訪問
-
-
call:代表一個簡單的函數(shù)調(diào)用;
#語法 <result> = [tail | musttail | notail ] call [fast-math flags] [cconv] [ret attrs] [addrspace(<num>)] <ty>|<fnty> <fnptrval>(<function args>) [fn attrs] [ operand bundles ]可選的
tail和musttail標(biāo)記優(yōu)化器應(yīng)執(zhí)行尾部調(diào)用優(yōu)化notail標(biāo)記用于防止執(zhí)行尾部調(diào)用優(yōu)化
-
ret:該指令表示函數(shù)返回
#語法 ret <type> <value> ; Return a value from a non-void function ret void ; Return from void function #示例 ret i32 5 ; Return an integer value of 5 ret void ; Return from a void function ret { i32, i8 } { i32 4, i8 2 } ; Return a struct of values 4 and 2 -
bitcast...to:
bitcast將value的類型轉(zhuǎn)換為類型ty2而不改變它的任何位bits#語法 <result> = bitcast <ty> <value> to <ty2> ; yields ty2 其他:
i32:代表32-bit的整數(shù),i8:代表8-bit的整數(shù);
代碼生成
生成目標(biāo)架構(gòu)的匯編代碼。
終端輸入:
#生成目標(biāo)架構(gòu)的匯編代碼
clang -S -fobjc-arc main.m -o main.s
輸出結(jié)果:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 6
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp #將%rbp的內(nèi)容壓棧,保存棧幀到%rsp中
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp # 將棧指針傳送至%rbp中,設(shè)置當(dāng)前棧幀
.cfi_def_cfa_register %rbp
subq $32, %rsp # 棧指針 - 32 (申請32個字節(jié)的空間)
movl $0, -4(%rbp)# 將 0 傳送至存儲器中,存儲器位置為: M[-4 + %rbp]
movl %edi, -8(%rbp) # 將%edi的內(nèi)容 傳送至存儲器中,存儲器位置為: M[-8 + %rbp]
movq %rsi, -16(%rbp)# 將%rsi的內(nèi)容 傳送至存儲器中,存儲器位置為: M[-16 + %rbp]
callq _objc_autoreleasePoolPush #調(diào)用_objc_autoreleasePoolPush
leaq L__unnamed_cfstring_(%rip), %rcx #將`L__unnamed_cfstring_(%rip)`的有效地址寫入`%rcx`中
movq %rcx, %rdi # 將%rcx的內(nèi)容 傳送至寄存器%rdi
movq %rax, -32(%rbp) ## 8-byte Spill # 將%rax的內(nèi)容 傳送至存儲器中,存儲器位置為: M[-32 + %rbp]
movb $0, %al # 將立即數(shù)0 傳送至寄存器的低八位的單字節(jié)寄存器`%al`中
callq _NSLog #調(diào)用 _NSLog
leaq L__unnamed_cfstring_.2(%rip), %rcx
leaq L__unnamed_cfstring_.4(%rip), %rdx
movq %rcx, %rdi # 將%rcx的內(nèi)容 傳送至寄存器%rdi
movq %rdx, %rsi #將%rdx的內(nèi)容 傳送至寄存器%rsi
movb $0, %al # 將立即數(shù)0 傳送至寄存器的低八位的單字節(jié)寄存器`%al`中
callq _NSLog #調(diào)用 _NSLog
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rcx
movq %rcx, %rdi
callq _objc_alloc_init
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movq _OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq *_objc_msgSend@GOTPCREL(%rip)
xorl %r8d, %r8d # 使用異或?qū)拇嫫鱜%r8d`清0
movl %r8d, %esi
leaq -24(%rbp), %rax
movq %rax, %rdi
callq _objc_storeStrong
movq -32(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax # 使用異或?qū)拇嫫鱜%eax`清0
addq $32, %rsp
popq %rbp #將%rbp的內(nèi)容彈出棧
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello, World!"
.section __DATA,__cfstring
.p2align 3 ## @_unnamed_cfstring_
L__unnamed_cfstring_:
.quad ___CFConstantStringClassReference
.long 1992 ## 0x7c8
.space 4
.quad L_.str
.quad 13 ## 0xd
.section __TEXT,__cstring,cstring_literals
L_.str.1: ## @.str.1
.asciz "%@"
.section __DATA,__cfstring
.p2align 3 ## @_unnamed_cfstring_.2
L__unnamed_cfstring_.2:
.quad ___CFConstantStringClassReference
.long 1992 ## 0x7c8
.space 4
.quad L_.str.1
.quad 2 ## 0x2
.section __TEXT,__ustring
.p2align 1 ## @.str.3
l_.str.3:
.short 20320 ## 0x4f60
.short 22909 ## 0x597d
.short 65292 ## 0xff0c
.short 19990 ## 0x4e16
.short 30028 ## 0x754c
.short 0 ## 0x0
.section __DATA,__cfstring
.p2align 3 ## @_unnamed_cfstring_.4
L__unnamed_cfstring_.4:
.quad ___CFConstantStringClassReference
.long 2000 ## 0x7d0
.space 4
.quad l_.str.3
.quad 5 ## 0x5
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_Person
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "share"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
匯編指令
所有以.開頭的行,都是指導(dǎo)編譯器與鏈接器的命令。
-
.section指定匯編器將生成的匯編代碼,寫入對應(yīng)的區(qū)section。語法:
.section segname , sectname [[[ , type ] , attribute ] , sizeof_stub ]示例:
#`regular`類型 表示該區(qū)存放程序指令或初始化數(shù)據(jù) #`pure_instructions`屬性 表示此區(qū)僅包含機(jī)器指令 .section __TEXT,__text,regular,pure_instructions #`cstring_literals`類型 表示該區(qū)存放以null結(jié)尾的c字符串 .section __TEXT,__cstring,cstring_literals .global symbol_name標(biāo)記符號為外部符號;-
.align對齊指令,指定匯編代碼的對齊方式語法:
.align align_expression [ , 1byte_fill_expression [,max_bytes_to_fill]] .p2align align_expression [ , 1byte_fill_expression [,max_bytes_to_fill]] .p2alignw align_expression [ , 2byte_fill_expression [,max_bytes_to_fill]] .p2alignl align_expression [ , 4byte_fill_expression [,max_bytes_to_fill]] .align32 align_expression [ , 4byte_fill_expression [,max_bytes_to_fill]]示例:
# 以16(2^4)字節(jié)的方式對齊,不足的使用0x90補(bǔ)齊 .p2align 4, 0x90 -
CFA在棧上分配的內(nèi)存區(qū)域,稱為“調(diào)用幀”。調(diào)用幀由棧上的地址標(biāo)識。我們將此地址稱為CFA(
Canonical Frame Address)。通常,將CFA定義為前一幀調(diào)用者上的棧指針的值(可能與當(dāng)前幀的值不同)。An area of memory that is allocated on a stack called a “call frame.” The call frame is identified by an address on the stack. We refer to this address as the Canonical Frame Address or CFA. Typically, the CFA is defined to be the value of the stack pointer at the call site in the previous frame (which may be different from its value on entry to the current frame).引自DWARF規(guī)范-6.4
.cfi_def_cfa_offset OFFSET:cfi_def_cfa_offset指令用來修改計(jì)算CFA的規(guī)則。注意:OFFSET是絕對偏移量,它會被加到幀指針寄存%ebp或者%rbp上,重新計(jì)算CFA的地址。.cfi_def_cfa REGISTER, OFFSET:cfi_def_cfa這個指令從寄存器中獲取地址并且加上這個OFFSET。-
.cfi_def_cfa_register REGISTER:cfi_def_cfa_register這個指令讓%ebp或%rbp被設(shè)置為新值且偏移量保持不變。上述設(shè)置只是為了用來輔助調(diào)試的,比如打斷點(diǎn),獲取調(diào)用堆棧信息。
-
CFI調(diào)用幀信息,英文全稱:
Call Frame Information。-
cfi_startproc,表示函數(shù)或過程開始。 -
.cfi_endproc,表示函數(shù)或過程結(jié)束。
-
更多細(xì)節(jié)請查看蘋果官網(wǎng)
匯編器
這個階段主要任務(wù)是運(yùn)行目標(biāo)架構(gòu)的匯編程序(匯編器),將編譯器的輸出轉(zhuǎn)換為目標(biāo)架構(gòu)的目標(biāo)(object)文件,即:.o文件。
終端輸入:
# -c : Run all of the above, plus the assembler, generating a target ".o" object file.
# -o : write to file
clang -c main.m -o main.o
clang -c Person.m -o person.o
輸出結(jié)果:
#使用命令查看生成文件
#file main.o person.o
#輸出
main.o: Mach-O 64-bit object x86_64
person.o: Mach-O 64-bit object x86_64
通過匯編器將可讀的匯編代碼,轉(zhuǎn)換為目標(biāo)架構(gòu)的目標(biāo)文件,最終輸出.o文件,也稱機(jī)器碼。
鏈接器
這個階段會運(yùn)行目標(biāo)架構(gòu)的鏈接器,將多個object文件合并成一個可執(zhí)行文件或動態(tài)庫。最終的輸出a.out、.dylib或.so。
在上述OC代碼示例中,Main函數(shù)中引用了Person類,因此若要生成可執(zhí)行的文件,需要將main.o與person.o進(jìn)行鏈接
終端輸入:
# no stage selection option
# If no stage selection option is specified, all stages above are run, and the
# linker is run to combine the results into an executable or shared library.
clang main.o person.o -o main
輸出結(jié)果:
"_NSLog", referenced from:
_main in main.o
-[Person share] in person.o
"_OBJC_CLASS_$_NSObject", referenced from:
_OBJC_CLASS_$_Person in person.o
"_OBJC_METACLASS_$_NSObject", referenced from:
_OBJC_METACLASS_$_Person in person.o
"___CFConstantStringClassReference", referenced from:
CFString in main.o
CFString in main.o
CFString in main.o
CFString in person.o
"__objc_empty_cache", referenced from:
_OBJC_METACLASS_$_Person in person.o
_OBJC_CLASS_$_Person in person.o
"_objc_alloc_init", referenced from:
_main in main.o
"_objc_autoreleasePoolPop", referenced from:
_main in main.o
"_objc_autoreleasePoolPush", referenced from:
_main in main.o
"_objc_msgSend", referenced from:
_main in main.o
ld: symbol(s) not found for architecture x86_64
clang-12: error: linker command failed with exit code 1 (
鏈接器未找到上述的符號,原因是我們代碼引入了Foundation庫,在生成可執(zhí)行文件時,未進(jìn)行鏈接。
在解決這個問題之前先介紹一下工具xcrun,使用xcrun可以從命令行定位和調(diào)用開發(fā)者工具
#--show-sdk-path : show selected SDK install path
xcrun --show-sdk-path
# 輸出`MacOSX.sdk`的路徑
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
基于此路徑鏈接我們的Foundation庫:
# -Wl,<arg> Pass the comma separated arguments in <arg> to the linker #傳參給鏈接器
# `xcrun --show-sdk-path` 等同 $(xcrun --show-sdk-path) 視為命令替換
clang main.o person.o -Wl,`xcrun --show-sdk-path`/System/Library/Frameworks/Foundation.framework/Foundation -o main
最終輸出如下圖:
執(zhí)行這個可執(zhí)行文件:
#執(zhí)行
./main
#輸出
2021-05-08 17:40:45.134 main[30561:1257231] Hello, World!
2021-05-08 17:40:45.135 main[30561:1257231] 你好,世界
2021-05-08 17:40:45.135 main[30561:1257231] 持之以恒
main文件查看:
file main
#輸出
main: Mach-O 64-bit executable x86_64
符號對比
符號表查看工具nm,允許我們查看Object文件的符號表內(nèi)容。
-
使用
nm終端工具,先觀察一下mian.o和person.o#輸入 nm -nm main.o person.o #輸出 (undefined) external _NSLog (undefined) external _OBJC_CLASS_$_Person (undefined) external ___CFConstantStringClassReference (undefined) external _objc_alloc_init (undefined) external _objc_autoreleasePoolPop (undefined) external _objc_autoreleasePoolPush (undefined) external _objc_msgSend 0000000000000000 (__TEXT,__text) external _main 00000000000000e8 (__TEXT,__ustring) non-external l_.str.3 00000000000000f8 (__DATA,__objc_classrefs) non-external _OBJC_CLASSLIST_REFERENCES_$_ 0000000000000108 (__DATA,__objc_selrefs) non-external _OBJC_SELECTOR_REFERENCES_ (undefined) external _NSLog (undefined) external _OBJC_CLASS_$_NSObject (undefined) external _OBJC_METACLASS_$_NSObject (undefined) external ___CFConstantStringClassReference (undefined) external __objc_empty_cache 0000000000000000 (__TEXT,__text) non-external -[Person share] 0000000000000024 (__TEXT,__ustring) non-external l_.str 0000000000000058 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_Person 00000000000000a0 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_Person 00000000000000c0 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_Person 0000000000000108 (__DATA,__objc_data) external _OBJC_METACLASS_$_Person 0000000000000130 (__DATA,__objc_data) external _OBJC_CLASS_$_Personexternal表示該符號針對當(dāng)前目標(biāo)文件不是私有的,與non-external相反。undefined表示該符號未找到。 -
使用
nm觀察一下可執(zhí)行文件main的符號表#輸入 nm -nm main #輸出 (undefined) external _NSLog (from Foundation) (undefined) external _OBJC_CLASS_$_NSObject (from libobjc) (undefined) external _OBJC_METACLASS_$_NSObject (from libobjc) (undefined) external ___CFConstantStringClassReference (from CoreFoundation) (undefined) external __objc_empty_cache (from libobjc) (undefined) external _objc_alloc_init (from libobjc) (undefined) external _objc_autoreleasePoolPop (from libobjc) (undefined) external _objc_autoreleasePoolPush (from libobjc) (undefined) external _objc_msgSend (from libobjc) (undefined) external dyld_stub_binder (from libSystem) 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 0000000100003e80 (__TEXT,__text) external _main #私有符號 0000000100003f00 (__TEXT,__text) non-external -[Person share] 0000000100008020 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_Person 0000000100008068 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_Person 0000000100008088 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_Person #非私有 00000001000080e0 (__DATA,__objc_data) external _OBJC_METACLASS_$_Person 0000000100008108 (__DATA,__objc_data) external _OBJC_CLASS_$_Person #私有符號 0000000100008130 (__DATA,__data) non-external __dyld_private
可以發(fā)現(xiàn)在經(jīng)過鏈接器處理后,為每個符號增加了來源。當(dāng)我們運(yùn)行可執(zhí)行文件時,會由動態(tài)鏈接器dyld通過這些來源對處于undifined的符號進(jìn)行解析,比如_NSLog,來自Foundation,在運(yùn)行時會在Foundation中找到指向它的函數(shù)地址,并最終調(diào)用執(zhí)行。
系統(tǒng)符號
目標(biāo)文件的顯示工具otool,可以查看Mach-O文件特定Section和Segment的內(nèi)容。
-
可執(zhí)行文件是知道它需要鏈接那些庫的
# -L :display the names and version numbers of the shared libraries that the object file uses otool -L main # 輸出 main: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1677.104.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1677.104.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)上述輸出我們發(fā)現(xiàn)在鏈接器生成可執(zhí)行文件時,我們通過
-Wl傳遞給鏈接器的Foundation的路徑與可執(zhí)行文件最終鏈接的Foundation路徑不一致。參數(shù)路徑下的文件內(nèi)容:
image.png -
.tbd文件the .tbd files are new "text-based stub libraries", that provide a much more compact version of the stub libraries for use in the SDK, and help to significantly reduce its download size. 引自stackoverflow
.tbd是個文本文件,提供的是SDK的更簡潔版本,明顯的降低Xcode的下載大小,具體內(nèi)容:.tbd文件內(nèi)容.tbd文件包含了與文件本身相關(guān)的元數(shù)據(jù),與架構(gòu)相關(guān)的信息,還有Foundation庫針對特定架構(gòu)的symbols,以及該庫所依賴的庫。并指定了Foundation庫的最終安裝路徑。Foundation -
查看系統(tǒng)符號
#輸入 nm -nm /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation | grep '_NSLog' #輸出NSlog的調(diào)用地址 000000000004ce6e (__TEXT,__text) external _NSLog
總結(jié)
OC代碼編譯時,首先會經(jīng)過預(yù)處理,接著進(jìn)行詞法分析將文本字符串Token化, 再通過語法與語義分析檢查代碼的類型與格式,最終生成AST,并在代碼優(yōu)化與生成階段,將AST轉(zhuǎn)換為底層的中間代碼LLVM IR,并最終生成目標(biāo)架構(gòu)的匯編代碼,交給匯編器進(jìn)行處理后,將可讀的匯編代碼轉(zhuǎn)換為目標(biāo)架構(gòu)的機(jī)器碼,即:.O文件,通過鏈接器,解決.O文件與庫的鏈接問題,最終根據(jù)特定的機(jī)器架構(gòu)生成可執(zhí)行文件。
參考資料
http://www.aosabook.org/en/llvm.html
https://en.m.wikibooks.org/wiki/GNU_C_Compiler_Internals/GNU_C_Compiler_Architecture