前言
編譯的主要任務(wù)是將源代碼文件作為輸入顶霞,最終輸出目標(biāo)文件署惯,這期間發(fā)生了什么?便是我們本篇文章要介紹的允坚。在開始之前我們先了解一下編譯器。
編譯器
編譯器(
compiler
)是一種計(jì)算機(jī)程序蛾号,它會(huì)將某種編程語言寫成的源代碼(原始語言)轉(zhuǎn)換成另一種編程語言(目標(biāo)語言)稠项。引自維基百科
傳統(tǒng)編譯器的架構(gòu),一般分三部分:
- 前端(
Frontend
):解析源代碼鲜结,檢查源代碼是否有錯(cuò)誤展运,并構(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)行時(shí)間,例如消除冗余計(jì)算怒允,并且通彻∪恚或多或少地獨(dú)立于編程語言和目標(biāo)代碼。 - 后端(
Backend
):也稱代碼生成器纫事,將代碼映射到目標(biāo)架構(gòu)的指令集上勘畔;其常見部分有:指令選擇,寄存器分配丽惶,指定調(diào)度炫七。
這種架構(gòu)的優(yōu)勢(shì)在于解耦合,實(shí)現(xiàn)一種編程語言钾唬,只需要實(shí)現(xiàn)它的前端万哪,對(duì)于優(yōu)化器與后端部分是可以復(fù)用的;支持新的目標(biāo)架構(gòu)抡秆,只需要實(shí)現(xiàn)它的后端即可奕巍;如果編譯器不是這種架構(gòu),三部分未分開琅轧,那么實(shí)現(xiàn)N
個(gè)編程語言伍绳,去支持M
個(gè)目標(biāo)架構(gòu),就需要實(shí)現(xiàn)N*M
個(gè)編譯器乍桂。
這種傳統(tǒng)編譯器的架構(gòu)有三個(gè)成功的案例:
-
Java
和.Net
虛擬機(jī);它們都提供了對(duì)JIT
編譯器和運(yùn)行時(shí)的支持效床,并且還定義了字節(jié)碼的格式(bytecode
)睹酌,這意味著任何可以編譯為字節(jié)碼的語言,都可以復(fù)用優(yōu)化器和JIT
(動(dòng)態(tài)編譯)和運(yùn)行時(shí)能力剩檀。 - 將輸入源轉(zhuǎn)換為
C
代碼(或其他某種語言)并通過現(xiàn)有的C
編譯器編譯 - 這種模式的最終成功實(shí)施是
GCC
憋沿,GCC
支持許多前端和后端,并擁有活躍而廣泛的貢獻(xiàn)者社區(qū)沪猴。
GCC
GCC
的概述
Xcode5
之前的版本中使用的是GCC
編譯器辐啄,由于GCC
采章,歷史悠久,體系結(jié)構(gòu)相對(duì)復(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ù),簡(jiǎn)化GENERIC
的復(fù)雜結(jié)構(gòu)嵌莉,將其轉(zhuǎn)換為一種中間表示形式稱為:GIMPLE
进萄,再轉(zhuǎn)換為另一種SSA
(static single assignment
)的表示形式也是用于優(yōu)化的,GCC
對(duì)SSA
樹執(zhí)行20
多種不同的優(yōu)化锐峭。經(jīng)過SSA
優(yōu)化后中鼠,該樹將轉(zhuǎn)換回GIMPLE
形式,用來生成一個(gè)RTL
樹沿癞,RTL
寄存器轉(zhuǎn)換語言援雇,全稱(register-transfer language
);RTL
是基于硬件的表示形式椎扬,與抽象的目標(biāo)架構(gòu)相對(duì)應(yīng)惫搏,處理寄存器分配、指令調(diào)度等蚕涤。RTL
優(yōu)化過程以RTL
形式對(duì)樹進(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++
)和動(dòng)態(tài)語言(運(yùn)行時(shí)改變其結(jié)構(gòu)的語言,如:OC
/JavaScript
)創(chuàng)造出動(dòng)態(tài)編譯技術(shù)控轿。 - 蘋果公司
2005
雇傭Chris Lattner
與他的團(tuán)隊(duì)為蘋果電腦開發(fā)應(yīng)用程序系統(tǒng)冤竹,LLVM
為現(xiàn)今macOS
與iOS
開發(fā)工具的一部分。 - 因
LLVM
對(duì)產(chǎn)業(yè)的貢獻(xiàn)茬射,2012
年獲得了ACM軟件系統(tǒng)獎(jiǎng)鹦蠕。獲得該獎(jiǎ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é)對(duì)輸入的代碼進(jìn)行解析噪伊,校驗(yàn)和分析錯(cuò)誤,然后將解析后的代碼轉(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
的唯一接口伍玖。這意味著寫一個(gè)LLVM
的前端只需要知道LLVM IR
即可诚啃,這是LLVM
的一個(gè)新穎的特性,也是LLVM
成功地被廣泛應(yīng)用的一個(gè)主要原因私沮。反觀GCC
編譯器,寫一個(gè)前端需要知道生成的GCC
樹的數(shù)據(jù)結(jié)構(gòu)以及使用GIMPLE
去寫GCC
的前端,GCC
后端需要知道RTL
是如何工作的仔燕。
LLVM IR
是前端輸出造垛,后端的輸入:
LLVM
廣義是指LLVM
整個(gè)架構(gòu),狹義指Clang
編譯器的后端晰搀。
Clang
Clang
是LLVM
的子項(xiàng)目五辽,是C
、C++
外恕、Objective C
語言的編譯器的前端杆逗。Clang
編譯Objective-C
代碼時(shí)速度為GCC
的3
倍。詳見維基百科鳞疲。
Clang
編譯過程
下面是一個(gè)基于簡(jiǎn)單的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
接下來腺毫,基于這個(gè)例子癣疟,我們使用終端逐步編譯,生成我們的可執(zhí)行文件潮酒,并最終控制臺(tái)打印我們的信息睛挚。
預(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)存遭贸,并斷行;
替換注釋為單個(gè)空格心软;
Tokenization
將輸入轉(zhuǎn)換為一系列預(yù)處理Tokens
壕吹;處理
#import
、#include
將所引的庫删铃,以遞歸的方式耳贬,插入到#import
或#include
所在的位置;替換宏定義猎唁;
條件編譯咒劲,根據(jù)條件包括或排除程序代碼的某些部分;
插入行標(biāo)記;
在預(yù)處理的輸出中腐魂,源文件名和行號(hào)信息會(huì)以# linenum filename flags
形式傳遞帐偎,這被稱為行標(biāo)記,代表著接下來的內(nèi)容開始于源文件filename
的第linenum
行蛔屹,而flags
則會(huì)有0
或者多個(gè)削樊,有1
、2
兔毒、3
漫贞、4
;如果有多個(gè)flags
時(shí)育叁,彼此使用分號(hào)隔開迅脐。詳見此處。
每個(gè)標(biāo)識(shí)的表示內(nèi)容如下:
-
1
表示一個(gè)新文件的開始 -
2
表示返回文件(包含另一個(gè)文件后) -
3
表示以下文本來自系統(tǒng)頭文件擂红,因此應(yīng)禁止某些警告 -
4
表示應(yīng)將以下文本視為包裝在隱式extern "C"
塊中仪际。
比如# 10 "main.m" 2
,表示導(dǎo)入Person.h
文件后回到main.m
文件的第10行昵骤。
詞法分析
詞法分析屬于預(yù)處理部分树碱,詞法分析的整個(gè)過程,主要是按照:標(biāo)識(shí)符变秦、 數(shù)字成榜、字符串文字、 標(biāo)點(diǎn)符號(hào)蹦玫,將我們的代碼分割成許多字符串序列赎婚,其中每個(gè)元素我們稱之為Token
,整個(gè)過程稱為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)儲(chǔ)Token
的內(nèi)部表示逻族。
更多關(guān)于Clang
參數(shù)的描述,請(qǐng)前往此處骄崩。
輸出結(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
包含信息(詳請(qǐng)見此處):
Sourece Location
:表示Token
開始的位置薄辅,比如:Loc=<main.m:11:5>
;Token Kind
:表示Token
的類型搁痛,比如:identifier
长搀、numeric_constant
、string_literal
鸡典;Flags
:詞法分析器和處理器跟蹤每個(gè)Token
的基礎(chǔ),目前有四個(gè)Flag
分別是:
StartOfLine
:表示這是每行開始的第一個(gè)Token
枪芒;LeadingSpace
:當(dāng)通過宏擴(kuò)展Token
時(shí)彻况,在Token
之前有一個(gè)空格字符。該標(biāo)志的定義是依據(jù)預(yù)處理器的字符串化要求而進(jìn)行的非常嚴(yán)格地定義舅踪。DisableExpand
:該標(biāo)志在預(yù)處理器內(nèi)部使用纽甘,用來表示identifier
令牌禁用宏擴(kuò)展。NeedsCleaning
:如果令牌的原始拼寫包含三字符組或轉(zhuǎn)義的換行符抽碌,則設(shè)置此標(biāo)志悍赢。
語法分析(Parsing
)與語義分析
此階段對(duì)輸入文件進(jìn)行語法分析,將預(yù)處理器生成的Tokens
轉(zhuǎn)換為語法分析樹货徙;一旦生成語法分析樹后左权,將會(huì)進(jìn)行語義分析,執(zhí)行類型檢查和代碼格式檢查痴颊。這個(gè)階段負(fù)責(zé)生成大多數(shù)編譯器警告以及語法分析過程的錯(cuò)誤赏迟。最終輸出AST
(抽象語法樹)。
Parser
的意義與作用
所謂 parser蠢棱,一般是指把某種格式的文本(字符串)轉(zhuǎn)換成某種數(shù)據(jù)結(jié)構(gòu)的過程锌杀。最常見的 parser,是把程序文本轉(zhuǎn)換成編譯器內(nèi)部的一種叫做“抽象語法樹”(AST)的數(shù)據(jù)結(jié)構(gòu)泻仙。摘自對(duì) 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 :表示一個(gè)聲明
declaration
或者一個(gè)定義definition
,比如:變量漩蟆,函數(shù)垒探,結(jié)構(gòu)體,typedef
怠李; -
DeclContext :用來聲明表示上下文的特定
decl
類型的基類圾叼; -
Stmt :表示一條陳述
statement
; -
Expr:在
Clang
的語法樹中也表示一條陳述statements
;
代碼優(yōu)化和生成
這個(gè)階段主要任務(wù)是將AST
轉(zhuǎn)換為底層中間的代碼LLVM IR
蛤克,并且最終生成機(jī)器碼;期間負(fù)責(zé)生成目標(biāo)架構(gòu)的代碼以及優(yōu)化生成的代碼夷蚊。最終輸出.s
文件(匯編文件)构挤。
LLVM IR
有三種格式:
- 文本格式:
.ll
文件 - 內(nèi)存中用以優(yōu)化自身時(shí),執(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)化的時(shí)間比較長(zhǎng)姻氨,可能會(huì)生成更大的代碼 -
-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)一鏈接單元的符號(hào),而非外部替換的符號(hào)
; 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
組成的功戚,每個(gè)Module
是輸入程序的翻譯單元娶眷。每個(gè)Module
都是由functions
、global variables
和symbol table entries
組成啸臀。Module
會(huì)通過LLVM
鏈接器組合到一起届宠,鏈接器會(huì)合并函數(shù)以及全局變量的定義,解決前置聲明以及合并符號(hào)表乘粒。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í)。元數(shù)據(jù)的兩個(gè)原語:元數(shù)據(jù)字符串和元數(shù)據(jù)節(jié)點(diǎn)旦棉。- 元數(shù)據(jù)字符串:用
""
引起來的字符串齿风,以!
作為前綴诉探。如:!"clang version 12.0.0"
- 元數(shù)據(jù)節(jié)點(diǎn):用
{}
括起來竹宋,使用,
隔開多個(gè)元素仍源,以!
作為前綴哗总。如:!{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)兩個(gè)使用了appending
的全局變量鏈接到一起的時(shí)候穷娱,這兩個(gè)全局的數(shù)組會(huì)被拼接到一起。 -
internal
:與private
相似纪他,但該值在目標(biāo)文件中顯示為本地符號(hào)鄙煤。與C
語言中static
關(guān)鍵字的概念相對(duì)應(yīng)。
-
屬性組:屬性組是
IR
中的對(duì)象(函數(shù)茶袒、全局變量)引用的屬性組合。它們對(duì)于保持.ll文件的可讀性很重要凉馆,因?yàn)樵S多函數(shù)將使用相同的屬性集薪寓。如上述IR
中的#0~#4
。-
函數(shù)屬性:被用來傳遞一個(gè)函數(shù)附加信息澜共。函數(shù)屬性被認(rèn)為是函數(shù)的一部分向叉,而不是函數(shù)類型,所以不同的函數(shù)屬性可以有相同的函數(shù)類型嗦董。上述
IR
中用到最多的函數(shù)屬性:-
nounwind
:表示函數(shù)不會(huì)拋出異常 -
nonlazybind
:阻止函數(shù)中某些符號(hào)的延遲綁定母谎。
-
-
參數(shù)屬性:函數(shù)的返回類型以及每個(gè)參數(shù)都有與之關(guān)聯(lián)的參數(shù)屬性集合。被用來傳遞一個(gè)函數(shù)的返回值與參數(shù)的附加信息京革。參數(shù)屬性是函數(shù)的一部分奇唤,而不是函數(shù)類型,所以有不同參數(shù)屬性的函數(shù)可以有相同的參數(shù)類型匹摇。上述
IR
中用到最多的參數(shù)屬性:-
nocapture
:表示函數(shù)調(diào)用不會(huì)捕獲參數(shù)的指針咬扇,這個(gè)屬性對(duì)于返回值是無效的,僅適用于參數(shù)廊勃。 -
readnone
:應(yīng)用于參數(shù)表示函數(shù)不會(huì)取消對(duì)此參數(shù)指針的引用懈贺。
-
-
-
@
為全局標(biāo)識(shí)符。以其開頭標(biāo)識(shí)函數(shù)坡垫,全局變量梭灿; -
%
為本地標(biāo)識(shí)符。以其開頭標(biāo)識(shí)寄存器名稱冰悠,類型堡妒; - 標(biāo)識(shí)符的不同的格式:
- 命名值,表示為以上述標(biāo)識(shí)符為前綴的字符串屿脐,如:
%struct._ivar_t
涕蚤、@.str
- 未命名值宪卿,表示為以上述標(biāo)識(shí)為前綴的無符號(hào)的數(shù)值,如:
%0
万栅、%1
- 命名值,表示為以上述標(biāo)識(shí)符為前綴的字符串屿脐,如:
-
-
#語法 %T1 = type { <type list> } ; Identified normal struct type %T2 = type <{ <type list> }> ; Identified packed struct type #表示結(jié)構(gòu)體的對(duì)齊方式為1字節(jié) #示例 {i32, i32} %mytype = type { %mytype*, i32 }
-
#語法 [<# elements> x <elementtype>] #語義 `elements`是個(gè)`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)不會(huì)被修改。 -
unnamed_addr
:表示該變量的地址并不重要烦粒,僅指示內(nèi)容休溶。 -
local_unnamed_addr
:表示變量的地址在module
內(nèi)并不重要。
-
-
Runtime Preemption Specifiers:運(yùn)行時(shí)搶占說明符扰她。全局變量兽掰,函數(shù)和別名可以具有一個(gè)可選的運(yùn)行時(shí)搶占說明符。如果未明確指定搶占說明符徒役,則假定該符號(hào)為
dso_preemptable
孽尽。-
dso_preemptable
:表示函數(shù)或者變量在運(yùn)行時(shí)會(huì)被外部的鏈接單元替換 -
dso_local
:表示函數(shù)或變量將解析為同一鏈接單元中的符號(hào)。即使定義不在此編譯單元內(nèi)忧勿,也將生成直接訪問
-
-
call:代表一個(gè)簡(jiǎn)單的函數(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 (申請(qǐng)32個(gè)字節(jié)的空間)
movl $0, -4(%rbp)# 將 0 傳送至存儲(chǔ)器中坎拐,存儲(chǔ)器位置為: M[-4 + %rbp]
movl %edi, -8(%rbp) # 將%edi的內(nèi)容 傳送至存儲(chǔ)器中,存儲(chǔ)器位置為: M[-8 + %rbp]
movq %rsi, -16(%rbp)# 將%rsi的內(nèi)容 傳送至存儲(chǔ)器中养匈,存儲(chǔ)器位置為: 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)容 傳送至存儲(chǔ)器中哼勇,存儲(chǔ)器位置為: 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
指定匯編器將生成的匯編代碼猴蹂,寫入對(duì)應(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)記符號(hào)為外部符號(hào)楣嘁;-
.align
對(duì)齊指令磅轻,指定匯編代碼的對(duì)齊方式語法:
.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é)的方式對(duì)齊,不足的使用0x90補(bǔ)齊 .p2align 4, 0x90
-
CFA
在棧上分配的內(nèi)存區(qū)域逐虚,稱為“調(diào)用幀”聋溜。調(diào)用幀由棧上的地址標(biāo)識(shí)。我們將此地址稱為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
是絕對(duì)偏移量把曼,它會(huì)被加到幀指針寄存%ebp
或者%rbp
上杨帽,重新計(jì)算CFA
的地址。.cfi_def_cfa REGISTER, OFFSET
:cfi_def_cfa
這個(gè)指令從寄存器中獲取地址并且加上這個(gè)OFFSET
嗤军。-
.cfi_def_cfa_register REGISTER
:cfi_def_cfa_register
這個(gè)指令讓%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é)請(qǐng)查看蘋果官網(wǎng)
匯編器
這個(gè)階段主要任務(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ī)器碼古掏。
鏈接器
這個(gè)階段會(huì)運(yùn)行目標(biāo)架構(gòu)的鏈接器损话,將多個(gè)object
文件合并成一個(gè)可執(zhí)行文件或動(dòng)態(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 (
鏈接器未找到上述的符號(hào),原因是我們代碼引入了Foundation
庫钝计,在生成可執(zhí)行文件時(shí)恋博,未進(jìn)行鏈接。
在解決這個(gè)問題之前先介紹一下工具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í)行這個(gè)可執(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
符號(hào)對(duì)比
符號(hào)表查看工具nm
,允許我們查看Object
文件的符號(hào)表內(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_$_Person
external
表示該符號(hào)針對(duì)當(dāng)前目標(biāo)文件不是私有的,與non-external
相反荣德。undefined
表示該符號(hào)未找到闷煤。 -
使用
nm
觀察一下可執(zhí)行文件main
的符號(hào)表#輸入 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 #私有符號(hào) 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 #私有符號(hào) 0000000100008130 (__DATA,__data) non-external __dyld_private
可以發(fā)現(xiàn)在經(jīng)過鏈接器處理后童芹,為每個(gè)符號(hào)增加了來源。當(dāng)我們運(yùn)行可執(zhí)行文件時(shí)鲤拿,會(huì)由動(dòng)態(tài)鏈接器dyld
通過這些來源對(duì)處于undifined
的符號(hào)進(jìn)行解析假褪,比如_NSLog
,來自Foundation
皆愉,在運(yùn)行時(shí)會(huì)在Foundation
中找到指向它的函數(shù)地址嗜价,并最終調(diào)用執(zhí)行。
系統(tǒng)符號(hào)
目標(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í)行文件時(shí),我們通過
-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
是個(gè)文本文件瑟由,提供的是SDK
的更簡(jiǎn)潔版本,明顯的降低Xcode
的下載大小冤寿,具體內(nèi)容:.tbd文件內(nèi)容.tbd
文件包含了與文件本身相關(guān)的元數(shù)據(jù)歹苦,與架構(gòu)相關(guān)的信息,還有Foundation
庫針對(duì)特定架構(gòu)的symbols
督怜,以及該庫所依賴的庫殴瘦。并指定了Foundation
庫的最終安裝路徑。Foundation -
查看系統(tǒng)符號(hào)
#輸入 nm -nm /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation | grep '_NSLog' #輸出NSlog的調(diào)用地址 000000000004ce6e (__TEXT,__text) external _NSLog
總結(jié)
OC
代碼編譯時(shí)号杠,首先會(huì)經(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