暗恋抄·星月樱雪外传·少年的星月

前言

2000年,俄亥俄大学厄巴纳-香槟分校(University of Illinois at
Urbana-Champaign 简称UIUC)那所拥有世界声誉的头等公立探讨型高校的 克里斯Lattner(他的 twitter
@clattner_llvm
) 开发了一个叫作 Low Level Virtual Machine
的编译器开发工具套件,后来波及范围更为大,可以用来常规编译器,JIT编译器,汇编器,调试器,静态分析工具等一密密麻麻跟编程语言相关的干活,于是就把简称
LLVM 那些简称作为了正式的名字。Chris Lattner 后来又开发了 Clang,使得
LLVM 直接挑战 GCC 的身价。二〇一二年,LLVM 得到美利坚联邦合众国总计机学会 ACM
的软件系统大奖,和 UNIX,WWW,TCP/IP,Tex,JAVA 等万分。

克莉丝 Lattner 生于 1978 年,二〇〇五年参预苹果,将苹果采纳的 GCC 周密转为
LLVM。二零一零年初始着力开发 Swift 语言。

iOS 开发中 Objective-C 是 Clang / LLVM 来编译的。

swift 是 斯威夫特 / LLVM,其中 斯威夫特 前端会多出 SIL optimizer,它会把
.swift 生成的中间代码 .sil 属于 High-Level IR, 因为 swift
在编译时就做到了艺术绑定直接通过地点调用属于强类型语言,方法调用不再是像OC那样的新闻发送,那样编译就足以得到更加多的音讯用在末端的后端优化上。

LLVM是一个模块化和可选取的编译器和工具链技术的成团,Clang 是 LLVM
的子项目,是 C,C++ 和 Objective-C 编译器,目的是提供惊人的敏捷编译,比
GCC 快3倍,其中的 clang static analyzer
重假如进行语法分析,语义分析和转移中间代码,当然这么些历程会对代码进行自我批评,出错的和须要告诫的会标注出来。LLVM
宗旨库提供一个优化器,对流行的 CPU 做代码生成援救。lld 是 Clang / LLVM
的内置链接器,clang 必须调用链接器来发生可执行文件。

LLVM 比较有风味的一点是它能提供一种代码编写卓越的中间表示
IR,那代表它可以看成七种语言的后端,这样就可见提供语言非亲非故的优化同时仍是可以方便的针对各种CPU 的代码生成。

LLVM 还用在 Gallium3D 中进行 JIT 优化,Xorg 中的 pixman 也有考虑采取LLVM 优化执行过程, LLVM-Lua 用LLVM 来编译 lua 代码, gpuocelot 使用
LLVM 可以让 CUDA 程序无需再一次编译就可以在多种 CPU 机器上跑。

此间是 Clang 官方详细文档: Welcome to Clang’s documentation! — Clang
4.0
documentation

那篇是对 LLVM 架构的一个概述: The Architecture of Open Source
Applications

将编译器此前对于编译的前生今生也是要求通晓的,比如回答下那些题材,编译器程序是用什么编译的?看看
《linkers and
loaders》

那本书就了解了。

(1)

编译流程

在列出完整步骤以前可以先看个简易例子。看看是怎么样成功一遍编译的。

#import <Foundation/Foundation.h>
#define DEFINEEight 8

int main(){
    @autoreleasepool {
        int eight = DEFINEEight;
        int six = 6;
        NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
        int rank = eight + six;
        NSLog(@"%@ rank %d", site, rank);
    }
    return 0;
}

在命令行输入

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

那般可以精通到进程和关键的新闻。
翻开oc的c完成可以选择如下命令

clang -rewrite-objc main.m

查阅操作内部命令,可以行使 -### 命令

clang -### main.m -o main

想看清clang的整个进程,可以先通过-E查看clang在预处理处理那步做了什么。

clang -E main.m

实施完后得以看看文件

# 1 "/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 185 "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2

int main(){
    @autoreleasepool {
        int eight = 8;
        int six = 6;
        NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
        int rank = eight + six;
        NSLog(@"%@ rank %d", site, rank);
    }
    return 0;
}

其一进度的处理包涵宏的交替,头文件的导入,以及近似#if的处理。预处理已毕后就会开展词法分析,那里会把代码切成一个个
Token,比如大小括号,等于号还有字符串等。

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

接下来是语法分析,验证语法是不是科学,然后将装有节点组成抽象语法树 AST 。

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

姣好这么些手续后就足以起先IR中间代码的变动了,CodeGen
会负责将语法树自顶向下遍历逐步翻译成 LLVM IR,IR
是编译进程的前端的输出后端的输入。

clang -S -fobjc-arc -emit-llvm main.m -o main.ll

那边 LLVM 会去做些优化工作,在 Xcode
的编译设置里也得以设置优化级别-01,-03,-0s,仍能写些自己的
Pass,官方有相比较完好的 Pass 教程: Writing an LLVM Pass — LLVM 5
documentation

clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll

Pass 是 LLVM 优化办事的一个节点,一个节点做些事,一起加起来就构成了 LLVM
完整的优化和转化。

如果翻开了 bitcode
苹果会做越来越的优化,有新的后端架构仍可以用那份优化过的 bitcode
去变通。

clang -emit-llvm -c main.m -o main.bc

变迁汇编

clang -S -fobjc-arc main.m -o main.s

扭转目的文件

clang -fmodules -c main.m -o main.o

转变可执行文件,那样就可见履行看到输出结果

clang main.o -o main
执行
./main
输出
starming rank 14

下边是总体步骤:

  • 编译音信写入协理文件,创立文件架构 .app 文件
  • 拍卖文件打包音讯
  • 执行 CocoaPod 编译前脚本,checkPods Manifest.lock
  • 编译.m文件,使用 CompileC 和 clang 命令
  • 链接须要的 Framework
  • 编译 xib
  • 拷贝 xib ,资源文件
  • 编译 ImageAssets
  • 处理 info.plist
  • 执行 CocoaPod 脚本
  • 拷贝标准库
  • 创造 .app 文件和签字

自我先是次见落樱有雪的那天,她问我林某扬那几个名字的原故,我说那名字就顺口起的,若说真有由来,大约就在一个某字,大家在戏耍中擦肩而过也好,并肩做战也好,江湖遇见也好,生死不离也好,关了游戏大家都是陌生人某某。

Clang 编译 .m 文件

在 Xcode 编译过后,可以透过 Show the report navigator 里对应 target 的
build 中查看各种 .m 文件的 clang 参数音讯,这一个参数都是通过Build
Setting。

现实拿编译 AFSecurityPolicy.m 的新闻来探望。首先对职务举办描述。

CompileC DerivedData path/AFSecurityPolicy.o AFNetworking/AFNetworking/AFSecurityPolicy.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler

接下去对会更新工作途径,同时设置 PATH

cd /Users/didi/Documents/Demo/GitHub/GCDFetchFeed/GCDFetchFeed/Pods
    export LANG=en_US.US-ASCII
    export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"

接下去就是其实的编译命令

clang -x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc... -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot iPhoneSimulator10.1.sdk -fasm-blocks ... -I -F -c AFSecurityPolicy.m -o AFSecurityPolicy.o

clang 命令参数

-x 编译语言比如objective-c
-arch 编译的架构,比如arm7
-f 以-f开头的。
-W 以-W开头的,可以通过这些定制编译警告
-D 以-D开头的,指的是预编译宏,通过这些宏可以实现条件编译
-iPhoneSimulator10.1.sdk 编译采用的iOS SDK版本
-I 把编译信息写入指定的辅助文件
-F 需要的Framework
-c 标识符指明需要运行预处理器,语法分析,类型检查,LLVM生成优化以及汇编代码生成.o文件
-o 编译结果

他说才不是,我们也得以变成现实里的好对象,也足以平素都有关系。

构建 Target

编译工程中的第三方依赖库后会创设大家先后的
target,会按梯次输出如下的音讯:

Create product structure
Process product packaging
Run custom shell script 'Check Pods Manifest.lock'
Compile ... 各个项目中的.m文件
Link /Users/... 路径
Copy ... 静态文件
Compile asset catalogs
Compile Storyboard file ...
Process info.plist
Link Storyboards
Run custom shell script 'Embed Pods Frameworks'
Run custom shell script 'Copy Pods Resources'
...
Touch GCDFetchFeed.app
Sign GCDFetchFeed.app

从那么些信息可以看到在那些手续中会分别调用差别的命令行工具来推行。

那一天,我瞅着他,瞅着她在海口城外的树林中游嬉,望着他眼神中透出的对这一个江湖的惊诧和向往。我回忆了自身和另一个人先是次遇上时的风貌。

Target 在 Build 进度的主宰

在 Xcode 的 Project editor 中的 Build Setting,Build Phases 和 Build
Rules 可以控制编译的进程。

也是均等的那片江湖,也是相同的言语,也是相同的说着能够一向有挂钩。

Build Phases

创设可执行文件的条条框框。指定 target 的借助项目,在 target build 之前须求先
build 的器重性。在 Compile Source 中指定所有必须编译的公文,那个文件会基于
Build Setting 和 Build Rules 里的设置来处理。

在 Link Binary With Libraries
里会列出所有的静态库和动态库,它们会和编译生成的目的文件进行链接。

build phase 还会把静态资源拷贝到 bundle 里。

可以透过在 build phases 里添加自定义脚本来做些事情,比如像 CocoaPods
所做的那样。

最后,我笑了笑,对落樱有雪说:“傻丫头,人生哪得事事如意。”

Build Rules

点名差异文件类型怎么样编译。每条 build rule
指定了该类型怎么样处理以及出口在哪。可以增添一条新规则对一定文件类型添加处理办法。

(2)

Build Settings

在 build 的进程中相继阶段的选用的安装。

那款叫做《江湖情缘3》的玩耍本身早就玩了三年。

pbxproj工程文件

build 进程控制的那几个设置都会被封存在工程文件 .pbxproj
里。在那么些文件中得以找 rootObject 的 ID 值

rootObject = 3EE311301C4E1F0800103FA3 /* Project object */;

接下来根据这一个 ID 找到 main 工程的定义。

/* Begin PBXProject section */
        3EE311301C4E1F0800103FA3 /* Project object */ = {
            isa = PBXProject;
            ...
/* End PBXProject section */

在 targets 里会指向各类 taget 的定义

targets = (
    3EE311371C4E1F0800103FA3 /* GCDFetchFeed */,
    3EE311501C4E1F0800103FA3 /* GCDFetchFeedTests */,
    3EE3115B1C4E1F0800103FA3 /* GCDFetchFeedUITests */,
);

本着那几个 ID 就可见找到更详尽的概念地点。比如大家透过 GCDFetchFeed 这么些target 的 ID 找到定义如下:

3EE311371C4E1F0800103FA3 /* GCDFetchFeed */ = {
    isa = PBXNativeTarget;
    buildConfigurationList = 3EE311651C4E1F0800103FA3 /* configuration list for PBXNativeTarget "GCDFetchFeed" 
    buildPhases = (
        9527AA01F4AAE11E18397E0C /* Check Pods st.lock */,
        3EE311341C4E1F0800103FA3 /* Sources */,
        3EE311351C4E1F0800103FA3 /* Frameworks */,
        3EE311361C4E1F0800103FA3 /* Resources */,
        C3DDA7C46C0308459A18B7D9 /* Embed Pods Frameworks 
        DD33A716222617FAB49F1472 /* Copy Pods Resources 
    );
    buildRules = (
    );
    dependencies = (
    );
    name = GCDFetchFeed;
    productName = GCDFetchFeed;
    productReference = 3EE311381C4E1F0800103FA3 /* chFeed.app */;
    productType = "com.apple.product-type.application";
};

以此里面又有越来越多的 ID 可以博得越多的概念,其中 buildConfigurationList
指向了可用的布置项,包蕴 Debug 和 Release。可以见到还有
buildPhases,buildRules 和 dependencies
都能够透过这里索引找到更详尽的概念。

接下去详细的看看 Clang 所做的政工啊。

中间也玩过多少个中号,林某扬是自我最初的账号。

Clang Static Analyzer静态代码分析

可以在 llvm/clang/ Source Tree – Woboq Code
Browser

上查看 Clang 的代码。

Youtube上一个课程:The Clang AST – a Tutorial –
YouTube

静态分析前会对源代码分词成 Token,那些历程称为词法分析(Lexical
Analysis),在
TokensKind.def
里有 Clang 定义的所有 Token。通过下边的吩咐能够出口所有 token
和所在文件具体地方

clang -fmodules -E -Xclang -dump-tokens main.m

结果如下

annot_module_include '#import <Fo'      Loc=<main.m:2:1>
int 'int'    [StartOfLine]  Loc=<main.m:5:1>
identifier 'main'    [LeadingSpace] Loc=<main.m:5:5>
l_paren '('     Loc=<main.m:5:9>
r_paren ')'     Loc=<main.m:5:10>
l_brace '{'  [LeadingSpace] Loc=<main.m:5:12>
at '@'   [StartOfLine] [LeadingSpace]   Loc=<main.m:6:5>
identifier 'autoreleasepool'        Loc=<main.m:6:6>
l_brace '{'  [LeadingSpace] Loc=<main.m:6:22>
identifier 'NSString'    [StartOfLine] [LeadingSpace]   Loc=<main.m:7:9>
star '*'     [LeadingSpace] Loc=<main.m:7:18>
identifier 'a'      Loc=<main.m:7:19>
equal '='    [LeadingSpace] Loc=<main.m:7:21>
at '@'   [LeadingSpace] Loc=<main.m:7:23>
string_literal '"aaa"'      Loc=<main.m:7:24>
semi ';'        Loc=<main.m:7:29>
identifier 'NSLog'   [StartOfLine] [LeadingSpace]   Loc=<main.m:8:9>
l_paren '('     Loc=<main.m:8:14>
at '@'      Loc=<main.m:8:15>
string_literal '"hi %@"'        Loc=<main.m:8:16>
comma ','       Loc=<main.m:8:23>
identifier 'a'      Loc=<main.m:8:24>
r_paren ')'     Loc=<main.m:8:25>
semi ';'        Loc=<main.m:8:26>
r_brace '}'  [StartOfLine] [LeadingSpace]   Loc=<main.m:9:5>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<main.m:10:5>
numeric_constant '0'     [LeadingSpace] Loc=<main.m:10:12>
semi ';'        Loc=<main.m:10:13>
r_brace '}'  [StartOfLine]  Loc=<main.m:11:1>
eof ''      Loc=<main.m:11:2>

可以赢得每个 token 的项目,值还有类似 StartOfLine 的地方类型和
Loc=<main.m:11:1> 那几个样的具体地点。

跟着进行语法分析(Semantic Analysis)将 token 先根据语法组合成语义生成
VarDecl 节点,然后将那个节点根据层级关系构成抽象语法树 Abstract Syntax
Tree (AST)。打印语法树的授命

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

出口结果

TranslationUnitDecl 0x7fa80f018ad0 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7fa80f018fc8 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7fa80f018d20 '__int128'
|-TypedefDecl 0x7fa80f019028 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fa80f018d40 'unsigned __int128'
|-TypedefDecl 0x7fa80f0190b8 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7fa80f019080 'SEL *'
|   `-BuiltinType 0x7fa80f018f30 'SEL'
|-TypedefDecl 0x7fa80f019198 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7fa80f019140 'id' imported
|   `-ObjCObjectType 0x7fa80f019110 'id' imported
|-TypedefDecl 0x7fa80f019278 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7fa80f019220 'Class'
|   `-ObjCObjectType 0x7fa80f0191f0 'Class'
|-ObjCInterfaceDecl 0x7fa80f0192c8 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7fa80f019618 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fa80f019430 'struct __NSConstantString_tag'
|   `-Record 0x7fa80f019390 '__NSConstantString_tag'
|-TypedefDecl 0x7fa80f0196a8 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7fa80f019670 'char *'
|   `-BuiltinType 0x7fa80f018b60 'char'
|-TypedefDecl 0x7fa80f047978 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fa80f047920 'struct __va_list_tag [1]' 1 
|   `-RecordType 0x7fa80f0197a0 'struct __va_list_tag'
|     `-Record 0x7fa80f0196f8 '__va_list_tag'
|-ImportDecl 0x7fa80f0486b0 <main.m:2:1> col:1 implicit Foundation
|-FunctionDecl 0x7fa80f048738 <line:5:1, line:11:1> line:5:5 main 'int ()'
| `-CompoundStmt 0x7fa80f393998 <col:12, line:11:1>
|   |-ObjCAutoreleasePoolStmt 0x7fa80f393950 <line:6:5, line:9:5>
|   | `-CompoundStmt 0x7fa80f393928 <line:6:22, line:9:5>
|   |   |-DeclStmt 0x7fa80f3a3b38 <line:7:9, col:29>
|   |   | `-VarDecl 0x7fa80f3a3580 <col:9, col:24> col:19 used a 'NSString *' cinit
|   |   |   `-ObjCStringLiteral 0x7fa80f3a3648 <col:23, col:24> 'NSString *'
|   |   |     `-StringLiteral 0x7fa80f3a3618 <col:24> 'char [4]' lvalue "aaa"
|   |   `-CallExpr 0x7fa80f3938c0 <line:8:9, col:25> 'void'
|   |     |-ImplicitCastExpr 0x7fa80f3938a8 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
|   |     | `-DeclRefExpr 0x7fa80f3a3b50 <col:9> 'void (id, ...)' Function 0x7fa80f3a3670 'NSLog' 'void (id, ...)'
|   |     |-ImplicitCastExpr 0x7fa80f3938f8 <col:15, col:16> 'id':'id' <BitCast>
|   |     | `-ObjCStringLiteral 0x7fa80f393800 <col:15, col:16> 'NSString *'
|   |     |   `-StringLiteral 0x7fa80f3a3bb8 <col:16> 'char [6]' lvalue "hi %@"
|   |     `-ImplicitCastExpr 0x7fa80f393910 <col:24> 'NSString *' <LValueToRValue>
|   |       `-DeclRefExpr 0x7fa80f393820 <col:24> 'NSString *' lvalue Var 0x7fa80f3a3580 'a' 'NSString *'
|   `-ReturnStmt 0x7fa80f393980 <line:10:5, col:12>
|     `-IntegerLiteral 0x7fa80f393960 <col:12> 'int' 0
`-<undeserialized declarations>

TranslationUnitDecl 是根节点,表示一个源文件。Decl 表示一个宣称,Expr
代表表达式,Literal 表示字面量是新鲜的 Expr,Stmt 表示语句。

clang 静态分析是透过确立分析引擎和 checkers
所组成的架构,那部分效益可以通过 clang —analyze 命令情势调用。clang
static analyzer 分为 analyzer core 分析引擎和 checkers 两部分,所有
checker 都是按照底层分析引擎之上,通过分析引擎提供的作用可以编写新的
checker。

可以透过 clang –analyze -Xclang -analyzer-checker-help 来列出如今 clang
版本下所有 checker。假诺想编写自己的 checker,可以在 clang 项目的 lib /
StaticAnalyzer / Checkers 目录下找到实例参考,比如
ObjCUnusedIVarsChecker.cpp
用来检查未拔取定义过的变量。那种措施可以方便用户增加对代码检查规则或者对
bug
类型进行增加,但是那种架构也有不足,每执行完一条语句后,分析引擎会遍历所有
checker 中的回调函数,所以 checker 越来越多,速度越慢。通过 clang -cc1
-analyzer-checker-help 可以列出能调用的 checker,上边是常用 checker

debug.ConfigDumper              Dump config table
debug.DumpCFG                   Display Control-Flow Graphs
debug.DumpCallGraph             Display Call Graph
debug.DumpCalls                 Print calls as they are traversed by the engine
debug.DumpDominators            Print the dominance tree for a given CFG
debug.DumpLiveVars              Print results of live variable analysis
debug.DumpTraversal             Print branch conditions as they are traversed by the engine
debug.ExprInspection            Check the analyzer's understanding of expressions
debug.Stats                     Emit warnings with analyzer statistics
debug.TaintTest                 Mark tainted symbols as such.
debug.ViewCFG                   View Control-Flow Graphs using GraphViz
debug.ViewCallGraph             View Call Graph using GraphViz
debug.ViewExplodedGraph         View Exploded Graphs using GraphViz

这几个 checker 里最常用的是 DumpCFG,DumpCallGraph,DumpLiveVars 和
DumpViewExplodedGraph。

clang static analyzer 引擎大概分为
CFG,MemRegion,SValBuilder,ConstraintManager 和 ExplodedGraph
多少个模块。clang static analyzer 本质上就是 path-sensitive
analysis,要很好的敞亮 clang static analyzer 引擎就需求对 Data Flow
Analysis 有所明白,包含迭代数据流分析,path-sensitive,path-insensitive
,flow-sensitive等。

编译的概念(词法->语法->语义->IR->优化->CodeGen)在 clang
static analyzer 里四处可知,例如 Relaxed Live Variables Analysis
可以减去分析中的内存消耗,使用 mark-sweep 已毕 Dead Symbols 的去除。

clang static analyzer 提供了无数扶持方法,比如
SVal.dump(),MemRegion.getString 以及 Stmt 和 Dcel 提供的 dump
方法。Clang 抽象语法树 Clang AST 常见的 API 有 Stmt,Decl,Expr 和
QualType。在编排 checker 时会遭受 AST 的层级检查,那时有个很好的接口
StmtVisitor,这么些接口类似 RecursiveAS电视isitor。

全副 clang static analyzer 的输入是 AnalysisConsumer,接着会调
HandleTranslationUnit() 方法开展 AST 层级举办辨析或者举办 path-sensitive
分析。默许会根据 inline 的 path-sensitive 分析,打造 CallGraph,从顶层
caller 按照调用的涉嫌来分析,具体是拔取的 WorkList 算法,从 EntryBlock
初步一步步的效仿,那一个历程叫做 intra-procedural
analysis(IPA)。那个宪章进度还索要对内存举行模拟,clang static analyzer
的内存模型是基于《A Memory Model for Static Analysis of C
Programs》那篇杂谈而来,pdf地址:http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf
在clang里的有血有肉落到实处代码可以查阅那五个文件
MemRegion.h
RegionStore.cpp

上面举个大约例子看看 clang static analyzer 是怎么样对源码举办模拟的。

int main()
{
    int a;
    int b = 10;
    a = b;
    return a;
}

对应的 AST 以及 CFG

#----------------AST-------------------
# clang -cc1 -ast-dump
TranslationUnitDecl 0xc75b450 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0xc75b740 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'char *'
`-FunctionDecl 0xc75b7b0 <test.cpp:1:1, line:7:1> line:1:5 main 'int (void)'
  `-CompoundStmt 0xc75b978 <line:2:1, line:7:1>
    |-DeclStmt 0xc75b870 <line:3:2, col:7>
    | `-VarDecl 0xc75b840 <col:2, col:6> col:6 used a 'int'
    |-DeclStmt 0xc75b8d8 <line:4:2, col:12>
    | `-VarDecl 0xc75b890 <col:2, col:10> col:6 used b 'int' cinit
    |   `-IntegerLiteral 0xc75b8c0 <col:10> 'int' 10

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< a = b <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    |-BinaryOperator 0xc75b928 <line:5:2, col:6> 'int' lvalue '='
    | |-DeclRefExpr 0xc75b8e8 <col:2> 'int' lvalue Var 0xc75b840 'a' 'int'
    | `-ImplicitCastExpr 0xc75b918 <col:6> 'int' <LValueToRValue>
    |   `-DeclRefExpr 0xc75b900 <col:6> 'int' lvalue Var 0xc75b890 'b' 'int'
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    `-ReturnStmt 0xc75b968 <line:6:2, col:9>
      `-ImplicitCastExpr 0xc75b958 <col:9> 'int' <LValueToRValue>
        `-DeclRefExpr 0xc75b940 <col:9> 'int' lvalue Var 0xc75b840 'a' 'int'
#----------------CFG-------------------
# clang -cc1 -analyze -analyzer-checker=debug.DumpCFG
int main()
 [B2 (ENTRY)]
   Succs (1): B1

 [B1]
   1: int a;
   2: 10
   3: int b = 10;
   4: b
   5: [B1.4] (ImplicitCastExpr, LValueToRValue, int)
   6: a
   7: [B1.6] = [B1.5]
   8: a
   9: [B1.8] (ImplicitCastExpr, LValueToRValue, int)
  10: return [B1.9];
   Preds (1): B2
   Succs (1): B0

 [B0 (EXIT)]
   Preds (1): B1

CFG
将程序拆得更细,可以将执行的历程显示的更直观些,为了防止路径爆炸,函数
inline 的基准会安装的比较严苛,函数 CFG 块多时不会开展 inline
分析,模拟栈深度当先一定值不会举办 inline 分析,那个默许是5。

在MRC使用的是CFG那样的推行路径模拟,ARC就从未有过了,举个例证,没有任何尺度都回到,CFG就会报错,而AST就不会。

官方 AST 相关文档

自家手上的这把剑叫做“江湖老”,取“少年子弟江湖老”之意,曾是当时名动一时的稀有宝剑。

CodeGen 生成 IR 代码

将语法树翻译成 LLVM IR 中间代码,做为 LLVM Backend
输入的桥接语言。那样做的好处在序言里也涉及了,方便 LLVM Backend
给多语言做同样的优化,做到语言无关。

那几个进度中还会跟 runtime 桥接。

  • 各连串,方法,成员变量等的结构体的变更,并将其内置对应的Mach-O的section中。
  • Non-Fragile ABI 合成 OBJC_IVAR_$_ 偏移值常量。
  • ObjCMessageExpr 翻译成相应版本的 objc_msgSend,super 翻译成
    objc_msgSendSuper。
  • strong,weak,copy,atomic 合成 @property 自动完结 setter 和
    getter。
  • @synthesize 的处理。
  • 生成 block_layout 数据结构
  • __block 和 __weak
  • _block_invoke
  • ARC 处理,插入 objc_storeStrong 和 objc_storeWeak 等 ARC
    代码。ObjCAutoreleasePoolStmt 转 objc_autorealeasePoolPush /
    Pop。自动添加 [super dealloc]。给种种 ivar 的类合成
    .cxx_destructor 方法自动释放类的分子变量。

这把剑原本是自个儿在娱乐中进入的首先个帮会的帮主的佩剑,她在自我对那片江湖懵懵懂懂之时收了本人入帮,带着我游历江湖,望着行侠仗义。

Clang Attributes

attribute(xx) 的语法格式出现,是 Clang
提供的有的力所能及让开发者在编译进度中出席一些源码控制的法门。下边列一些会用到的用法:

直至有一天,她把那把佩剑给了本人,她说她要走了。

attribute((format(NSString, F, A))) 格式化字符串

能够查看 NSLog 的用法

FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;

// Marks APIs which format strings by taking a format string and optional varargs as arguments
#if !defined(NS_FORMAT_FUNCTION)
    #if (__GNUC__*10+__GNUC_MINOR__ >= 42) && (TARGET_OS_MAC || TARGET_OS_EMBEDDED)
    #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
    #else
    #define NS_FORMAT_FUNCTION(F,A)
    #endif
#endif

我问她,为啥是自己?那帮中高手如云,无论是副大当家仍旧他的学徒都比我强,为啥那把剑要给自身。

attribute((deprecated(s))) 版本弃用提醒

在编译进度中可见唤起开发者该格局如故性质已经被弃用

- (void)preMethod:( NSString *)string __attribute__((deprecated("preMethod已经被弃用,请使用newMethod")));
- (void)deprecatedMethod DEPRECATED_ATTRIBUTE; //也可以直接使用DEPRECATED_ATTRIBUTE这个系统定义的宏

她只是笑了笑,说:我有种预感,你会是以此帮会最后的守护人。

attribute((availability(os,introduced=m,deprecated=n, obsoleted=o,message=”” VA_ARGS))) 指明使用版本范围

os 指系统的本子,m 指明引入的本子,n 指明过时的版本,o
指完全不用的本子,message 可以写入些描述音信。

- (void)method __attribute__((availability(ios,introduced=3_0,deprecated=6_0,obsoleted=7_0,message="iOS3到iOS7版本可用,iOS7不能用")));

新兴,我成了一名大剑客,没埋没这把名剑。

attribute((unavailable(…))) 方法不可用提醒

这么些会在编译进度中报告方法不可用,如果采用了还会让编译失利。

三年后,从人间第一大帮退出的自己又重临了那一个早已无人的帮会,望着一片灰暗的帮会成员列表,看着毫无人烟的帮会领地,我猛然很想问她:你怎么知道我会是那帮会最恋旧的人?

attribute((unused))

从未被采用也不报警告

(3)

attribute((warn_unused_result))

不利用格局的重回值就会警告,近日 swift3
已经支撑该特性了。oc中也得以透过定义这些attribute来支撑。

自己在那几个娱乐有那些的小号,而中号的起名大多跟了某人欢愉食品的不良风气。

attribute((availability(swift, unavailable, message=_msg)))

OC 的章程不可以在 Swift 中动用。

自己最闻明的一个中号叫做没熟的烧麦,杀手榜第一,排在我上边的是她,热乎的汤圆。

attribute((cleanup(…))) 效能域截至时自动执行一个点名方法

功能域截止包蕴大括号甘休,return,goto,break,exception
等景况。这些动作是早日这一个目的的 dealloc 调用的。

Reactive Cocoa 中有个相比较好的行使范例,@onExit 那些宏,定义如下:

#define onExit \
    rac_keywordify \
    __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^

static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) {
    (*block)();
}

这么可以在就能够很便利的把需求成对现身的代码写在一块儿了。同样可以在
Reactive Cocoa 看到其行使

if (property != NULL) {
        rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property);
        if (attributes != NULL) {
            @onExit {
                free(attributes);
            };

            BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type;
            BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol");
            BOOL isBlock = strcmp(attributes->type, @encode(void(^)())) == 0;
            BOOL isWeak = attributes->weak;

            shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol;
        }
    }

可以看出 attributes 的装置和刑释解教都在联名使得代码的可读性获得了增强。

在没认识他前我已是江湖上大名鼎鼎的大剑客,她也是PVE榜的前十。

attribute((overloadable)) 方法重载

可见在 c
的函数上落到实处格局重载。即一律的函数名函数可以对两样参数在编译时亦可活动根据参数来挑选定义的函数

__attribute__((overloadable)) void printArgument(int number){
    NSLog(@"Add Int %i", number);
}

__attribute__((overloadable)) void printArgument(NSString *number){
    NSLog(@"Add NSString %@", number);
}

__attribute__((overloadable)) void printArgument(NSNumber *number){
    NSLog(@"Add NSNumber %@", number);
}

认识他后,大家一块做了凶手,她玩了一个剑客男号叫做阿修,我玩了一个黑衣女刀客叫做没熟的烧麦。

attribute((objc_designated_initializer)) 指定内部贯彻的开首化方法

  • 如果是 objc_designated_initializer 伊始化的格局必须调用覆盖已毕super 的 objc_designated_initializer 方法。
  • 假如不是 objc_designated_initializer 的开首化方法,不过该类有
    objc_designated_initializer 的开头化方法,那么必须调用该类的
    objc_designated_initializer 方法或者非
    objc_designated_initializer 方法,而不可知调用 super
    的此外开始化方法。

本人还记得她跟我走的那天,她们帮会的大当家凝眸跟自身说汤圆从玩这些游戏起就径直在打PVE,过的都是副本中杜门谢客的光景,现在您要带他去打PVP,涉足江湖的腥风血雨,那你不可以不照顾好他,不然固然我是个PVE玩家,将来也见你一遍杀你一遍。

attribute((objc_subclassing_restricted)) 指定不可能有子类

一定于 Java 里的 final 关键字,要是有子类继承就会出错。

本人握开端中的“江湖老”说那把剑就是承诺。

attribute((objc_requires_super)) 子类继承必须调用 super

宣示后子类在持续这几个法鼠时务须要调用
super,否则会出现编译警告,那么些可以定义一些必备实施的不二法门在 super
里提示使用者那一个措施的情节时少不了的。

后来,就算江湖波澜壮阔,我的剑也能把他护在身后,故而此剑抚平天下不平事。

attribute((const)) 重复调用相同数值参数优化再次回到

用以数值类型参数的函数,数次调用相同的数值型参数,重回是平等的,只在率先次是索要展开演算,前边只回去第二回的结果,那时编译器的一种优化处理方式。

不过,她最后依旧没留下来,所以此剑愧于世间有愧人。

attribute((constructor(PRIORITY))) 和 attribute((destructor(PRIORITY)))

PRIORITY 是指执行的优先级,main 函数执行从前会执行 constructor,main
函数执行后会执行 destructor,+load 会比 constructor
执行的更早点,因为动态链接器加载 Mach-O 文件时会先加载每个类,须求 +load
调用,然后才会调用所有的 constructor 方法。

通过这几个特点,可以做些相比有趣的政工,比如说类已经 load
完了,是还是不是足以在 constructor
中对想替换的类举行轮换,而不用加在特定类的 +load 方法里。

(4)

Clang 警告处理

先看看那一个

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        sizeLabel = [self sizeWithFont:font constrainedToSize:size lineBreakMode:NSLineBreakByWordWrapping];
#pragma clang diagnostic pop

假使没有#pragma clang 这么些概念,会报出 sizeWithFont
的主意会被舍弃的告诫,那么些加上这些点子自然是为着合作老系统,加上 ignored
“-Wdeprecated-declarations” 的成效是忽视那些警示。通过 clang diagnostic
push/pop 可以灵活的控制代码块的编译选项。

落樱有雪是自身在这么些娱乐里的尾声一个朋友。

行使 libclang 来进行语法分析

使用 libclang
里面提供的艺术对源文件进行语法分析,分析语法树,遍历语法树上每个节点。

运用那几个库可以直接运用 C 的 API,官方也提供了 python binding。还有开源的
node-js / ruby binding,还有 Objective-C的开源库 GitHub –
macmade/ClangKit: ClangKit provides an Objective-C frontend to LibClang.
Source tokenization, diagnostics and fix-its are actually
implemented.

写个 python 脚本来调用 clang

pip install clang

#!/usr/bin/python
# vim: set fileencoding=utf-8

import clang.cindex
import asciitree
import sys

def node_children(node):
    return (c for c in node.get_children() if c.location.file == sys.argv[1])

def print_node(node):
    text = node.spelling or node.displayname
    kind = str(node.kind)[str(node.kind).index('.')+1:]
    return '{} {}'.format(kind, text)

if len(sys.argv) != 2:
    print("Usage: dump_ast.py [header file name]")
    sys.exit()

clang.cindex.Config.set_library_file('/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib')
index = clang.cindex.Index.create()
translation_unit = index.parse(sys.argv[1], ['-x', 'objective-c'])

print asciitree.draw_tree(translation_unit.cursor,
                          lambda n: list(n.get_children()),
                          lambda n: "%s (%s)" % (n.spelling or n.displayname, str(n.kind).split(".")[1]))

基于语法树的剖析还足以本着字符串做加密。

自己回来这些娱乐的时候,唯有七个对象还在这片江湖。

LibTooling 对语法树完全的操纵

因为 LibTooling 能够统统控制语法树,那么可以做的工作就更加多了。

  • 可以更改 clang 生成代码的艺术。
  • 伸张更强的体系检查。
  • 听从自己的概念进行代码的自我批评分析。
  • 对源码做任意档次分析,甚至重写程序。
  • 给 clang 添加一些自定义的剖析,创造和谐的重构器。
  • 根据现有代码做出大量的修改。
  • 按照工程变更相关图片或文档。
  • 自我批评命名是或不是正规,仍能举行语言的变换,比如把 OC 语言转成JS或者
    Swift 。

法定有个文档开发者可以坚守这一个里面的证实去社团 LLVM,clang 和其工具:
Tutorial for building tools using LibTooling and LibASTMatchers — Clang
4.0
documentation

安份守己表明编译完结后跻身 LLVM 的目录 ~/llvm/tools/clang/tools/
在那了足以创制和谐的 clang 工具。那里有个范例: GitHub –
objcio/issue-6-compiler-tool: Example code for a standalone clang/llvm
tool.

可以从来 make 成一个二进制文件。

下边是检查 target 对象中是还是不是有照应的 action 方法存在检查的一个例证

@interface Observer
+ (instancetype)observerWithTarget:(id)target action:(SEL)selector;
@end

//查找消息表达式,observer 作为接受者,observerWithTarget:action: 作为 selector,检查 target 中是否存在相应的方法。
virtual bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
  if (E->getReceiverKind() == ObjCMessageExpr::Class) {
    QualType ReceiverType = E->getClassReceiver();
    Selector Sel = E->getSelector();
    string TypeName = ReceiverType.getAsString();
    string SelName = Sel.getAsString();
    if (TypeName == "Observer" && SelName == "observerWithTarget:action:") {
      Expr *Receiver = E->getArg(0)->IgnoreParenCasts();
      ObjCSelectorExpr* SelExpr = cast<ObjCSelectorExpr>(E->getArg(1)->IgnoreParenCasts());
      Selector Sel = SelExpr->getSelector();
      if (const ObjCObjectPointerType *OT = Receiver->getType()->getAs<ObjCObjectPointerType>()) {
        ObjCInterfaceDecl *decl = OT->getInterfaceDecl();
        if (! decl->lookupInstanceMethod(Sel)) {
          errs() << "Warning: class " << TypeName << " does not implement selector " << Sel.getAsString() << "\n";
          SourceLocation Loc = E->getExprLoc();
          PresumedLoc PLoc = astContext->getSourceManager().getPresumedLoc(Loc);
          errs() << "in " << PLoc.getFilename() << " <" << PLoc.getLine() << ":" << PLoc.getColumn() << ">\n";
        }
      }
    }
  }
  return true;
}

他俩一个叫七宝丸,一个名叫一弦清歌。

ClangPlugin

经过投机写个插件,比如下边写的 LibTooling 的 clang
工具,可以将以此插件动态的加载到编译器中,对编译举办支配,可以在 LLVM
的这些目录下查看一些范例 llvm/tools/clang/tools

动态化方案 DynamicCocoa 中就是采纳了一个将 OC 源码转 JS
的插件来进展代码的更换,JSPatch 是直接手写 JS
而尚未更换的历程,所以也就从不多出这一步,而鹅厂的OCS更猛,直接在端内写了个编译器。在
C 函数的调用上孙源有个 slides 可以看看: Calling Conventions in Cocoa
by
sunnyxx

bang 也有篇作品: 怎么动态调用 C 函数 « bang’s
blog

那三个方案作者都各自写了小说详细表达其达成方案。

滴滴的王康在做瘦身时也落到实处了一个自定义的 clang
插件,具体自定义插件的落到实处可以查看他的那小说
《基于clang插件的一种iOS包大小瘦身方案》

那就是说大家要协调入手做应该怎么入门呢,除了自己带的范例外还有些教程可以看看。

清歌和自己同样都是老江湖了,我们一块纵剑江湖的时候,现在游乐里的一大半人还不知底在哪。

编译后生成的二进制内容 Link Map File

在 Build Settings 里设置 Write Link Map File 为 Yes
后历次编译都会在指定目录生成这么一个文件。文件内容包涵 Object
files,Sections,Symbols。上边分别说说那么些情节

七宝丸是当下江湖中最烜赫一时的剑客,除了自己之外,没人知道她干什么会这么强,甚至每几次会合手法都比上一次有上扬。

Object files

本条片段的始末都是 .m 文件编译后的 .o 和须求 link 的 .a
文件。前边是文本编号,前边是文件路径。

清歌和我都老了,大家有朝一日都要离开这么些游戏,可能是自身无暇工作,可能是清歌忙于相亲。

Sections

此地描述的是各类 Section 在可执行文件中的地方和大小。每个 Section 的
Segment 的类型分为 __TEXT 代码段和 __DATA 数据段两种。

人间然后会是七宝丸的,但也毕竟会是落樱有雪的。

Symbols

Symbols 是对 Sections 举行了再细分。那里会讲述所有的 methods,ivar
和字符串,及它们对应的地点,大小,文件编号音信。

这片江湖就是那样,曾经的侠者销声匿迹,连神话都留不下,后来者代有人才出,直到这么些游戏彻底死去的那一天。

老是编译后生成的 dSYM 文件

在历次编译后都会变动一个 dSYM
文件,程序在履行中通过地方来调用方法函数,而 dSYM
文件里积存了函数地址映射,那样调用栈里的地址可以通过 dSYM
那一个映射表可以取得具体函数的地方。一般都会用来拍卖 crash
时获得到的调用栈 .crash 文件将其符号化。

可以通过 Xcode 举行符号化,将 .crash 文件,.dSYM 和 .app
文件放到同一个索引下,打开 Xcode 的 Window 菜单下的 organizer,再点击
Device tab,最终选中左侧的 Device Logs。接纳 import 将 .crash
文件导入就足以看到 crash 的详细 log 了。

还能通过命令行工具 symbolicatecrash 来手动符号化 crash log。同样先将
.crash 文件,.dSYM 和 .app 文件放到同一个索引下,然后输入上边的吩咐

export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
symbolicatecrash appName.crash appName.app > appName.log

在本人回来那几个娱乐的最终时段里,我看着落樱有雪新奇、憧憬、喜笑颜开、好玩的指南,我很难奉告她我的情感。

Mach-O 文件

笔录编译后的可执行文件,对象代码,共享库,动态加载代码和内存转储的文件格式。不一样于
xml
那样的公文,它只是二进制字节流,里面有两样的带有元音信的数据块,比如字节顺序,cpu
类型,块大小等。文件内容是不得以修改的,因为在 .app 目录中有个
_CodeSignature
的目录,里面蕴涵了程序代码的签名,那些签名的功效就是确保签名后 .app
里的文本,包蕴资源文件,Mach-O 文件都不可见转移。

Mach-O 文件包罗八个区域

  • Mach-O Header:包括字节顺序,magic,cpu 类型,加载指令的数额等
  • Load
    Commands:包罗众多情节的表,包含区域的岗位,符号表,动态符号表等。每个加载指令包括一个元音讯,比如指令类型,名称,在二进制中的地点等。
  • Data:最大的局地,包括了代码,数据,比如符号表,动态符号表等。

姑娘,你的江湖才刚刚初叶,我的江湖却已经停止。

Mach-O 文件的剖析

再经过一个例子来分析下:
这次用 xcrun 来

xcrun clang -v

先成立一个test.c的文本

touch test.c

编写里面的情节

vi test.c

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("hi there!\n");
    return 0;
}

编译运行,没有起名默许为 a.out

xcrun clang test.c
./a.out

a.out
就是编译生成的二进制文件,下边看看那些二进制文件时怎么转变的把。先看看输出的汇编代码

xcrun clang -S -o - test.c | open -f

出口的结果里 .
开首的行是汇编指令不是汇编代码,其余的都是汇编代码。先看看前几行

.section    __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 12
.globl  _main
.align  4, 0x90

.section 指令指定接下去执行哪一个段。

.globl 指令表达 _main 是一个外表符号,因为 main()
函数对于系统的话是索要调用它来运转实践文书的。

.align 提出后边代码的对齐情势,16(2^4) 字节对齐, 0x90 补齐。

探望接下去的 main 函数底部部分

_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp

_main 是函数开端的地方,二进制文件会有其一义务的引用。

.cfi_startproc 这些命令用于函数的始发,CFI 是 Call Frame Infomation
的缩写是调用帧音信的意味,在用 debugger 时实际上就是 stepping in / out
的一个调用帧。当现身 .cfi_endproc 时表示匹对竣事标记出 main()
函数为止。

pushq %rbp 是汇编代码,## BB#0: 那几个 label 里的。ABI 会让 rbp
这些寄存器的被爱惜起来,当函数调用重临时让 rbp 寄存器的值跟从前一样。
ABI 是 application binary interface
的缩写表示应用二进制接口,它指定了函数调用是怎么着在汇编代码层面上工作的。pushq
%rbp 将 rbp 的值 push 到栈中。

.cfi_def_cfa_offset 16 和 .cfi_offset %rbp, -16
会输出一些储藏室和调试新闻,确保调试器要使用那个音信时能够找到。

movq %rsp, %rbp 把一些变量放到栈上。

subq $32, %rsp 会将栈指针移动 32
个字节,就是函数调用的义务。旧的栈指针存在 rbp
里当作局地变量的基址,再立异堆栈指针到会选择的地方。

再看看 printf()

leaq    L_.str(%rip), %rax
movl    $0, -4(%rbp)
movl    %edi, -8(%rbp)
movq    %rsi, -16(%rbp)
movq    %rax, %rdi
movb    $0, %al
callq   _printf

leap 会将 L_.str 这几个指针加载到 rax 寄存器里。可以看看 L_.str 的定义

L_.str:                                 ## @.str
    .asciz  "hi there\n"

以此就是大家代码文件里定义的那一个字符串。

此间可以看看函数的三个参数分别保存在 edi 和 rsi
寄存器里,按照函数地址做了分化的撼动。

理所当然也可以看来在那些汇编代码还有可以优化的地点,因为那三个值并不曾用,却依旧被寄存器存储了。

printf() 是个可变参数的函数,按照 ABI
调用约定存储参数的寄存器数量存储在寄存器 al
中,可变所以数量设置为0,callq 会调用 printf() 函数。

接下去看看重回和函数的利落

xorl    %ecx, %ecx
movl    %eax, -20(%rbp)         ## 4-byte Spill
movl    %ecx, %eax
addq    $32, %rsp
popq    %rbp
retq
.cfi_endproc

xorl %ecx, %ecx 相当于将 ecx 寄存器设置为0。ABI 约定 eax
寄存器用来保存函数重返值,拷贝 ecx 到 eax 中,那样 main() 再次回到值就是0。

函数执行完会复苏堆栈指针,后面是 subq 32 是把 rsp 下移32字节,addq
就是前进归位。然后把 rbp 的值从栈里 pop 出来。ret
会读取出栈重临的地方,.cfi_endproc 和 .cfi_startproc 配对标记为止。

接下去是字符串输出

    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "hi there\n"

.subsections_via_symbols

同等 .section 指出进入一个新的段。最后 .subsections_via_symbols
是静态链接器用的。

接下去通过 size 工具来探视 a.out 里的 section。

xcrun size -x -l -m a.out

Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
    Section __text: 0x34 (addr 0x100000f50 offset 3920)
    Section __stubs: 0x6 (addr 0x100000f84 offset 3972)
    Section __stub_helper: 0x1a (addr 0x100000f8c offset 3980)
    Section __cstring: 0xa (addr 0x100000fa6 offset 4006)
    Section __unwind_info: 0x48 (addr 0x100000fb0 offset 4016)
    total 0xa6
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
    Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
    Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
    total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000

可以看看有七个 segment 和七个section。

在运转时,虚拟内存会把 segment
映射到进度的地址空间,虚拟内存会幸免将所有实践文书全体加载到内存。

__PAGEZERO segment 的尺寸是
4GB,不是文件真实大小,是规定进程地址空间前 4GB
被映射为不可实践,不可写和不可读。

__TEXT segment 包罗被执行的代码以只读和可进行的措施映射。

  • __text section 包蕴编译后的机器码。
  • __stubs 和 __stub_helper 是给动态链接器 dyld
    使用,可以允许延迟链接。
  • __cstring 可执行文件中的字符串。
  • __const 不可变的常量。

__DATA segment 以可读写和不足实践的措施映射,里面是会被更改的多寡。

  • __nl_symbol_ptr 非延缓指针。可执行文件加载同时加载。
  • __la_symbol_ptr
    延迟符号指针。延迟用于可执行文件中调用未定义的函数,可执行文件里不曾包罗的函数会延迟加载。
  • __const 须要重定向的常量,例如 char * const c = “foo”;
    c指针指向可变的数码。
  • __bss 不用起初化的静态变量,例如 static int i; ANSI C
    标准规定静态变量必须设置为0。运行时静态变量的值是可修改的。
  • __common 包罗外部全局变量。例如在函数外定义 int i;
  • __dyld 是section占位符,用于动态链接器。

更加多 section 类型介绍可以查阅苹果文档: OS X Assembler
Reference

接下去用 otool 查看下 section 里的内容:

xcrun otool -s __TEXT __text a.out

a.out:
Contents of (__TEXT,__text) section
0000000100000f50    55 48 89 e5 48 83 ec 20 48 8d 05 47 00 00 00 c7 
0000000100000f60    45 fc 00 00 00 00 89 7d f8 48 89 75 f0 48 89 c7 
0000000100000f70    b0 00 e8 0d 00 00 00 31 c9 89 45 ec 89 c8 48 83 
0000000100000f80    c4 20 5d c3 

这一个重临的始末很难读,加个 – v 就足以查看反汇编代码了, -s __TEXT
__text 有个缩写 -t

xcrun otool -v -t a.out

a.out:
(__TEXT,__text) section
_main:
0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    subq    $0x20, %rsp
0000000100000f58    leaq    0x47(%rip), %rax
0000000100000f5f    movl    $0x0, -0x4(%rbp)
0000000100000f66    movl    %edi, -0x8(%rbp)
0000000100000f69    movq    %rsi, -0x10(%rbp)
0000000100000f6d    movq    %rax, %rdi
0000000100000f70    movb    $0x0, %al
0000000100000f72    callq   0x100000f84
0000000100000f77    xorl    %ecx, %ecx
0000000100000f79    movl    %eax, -0x14(%rbp)
0000000100000f7c    movl    %ecx, %eax
0000000100000f7e    addq    $0x20, %rsp
0000000100000f82    popq    %rbp
0000000100000f83    retq

看起来是还是不是很熟悉,和眼前的编译时大都,不相同的即使从未汇编指令。

今天来看望可执行文件。

由此 otool 来看看可执行文件底部, 通过 -h 可以打印出尾部新闻:

otool -v -h a.out

Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    15       1200   NOUNDEFS DYLDLINK TWOLEVEL PIE

mach_header 结构体

struct mach_header {
  uint32_t      magic;
  cpu_type_t    cputype;
  cpu_subtype_t cpusubtype;
  uint32_t      filetype;
  uint32_t      ncmds;
  uint32_t      sizeofcmds;
  uint32_t      flags;
};

cputype 和 cpusubtype 规定可执行文件可以在什么样目的架构运行。ncmds 和
sizeofcmds 是加载命令。通过 -l 可以查看加载命令

otool -v -l a.out | open -f

加载命令结构体

struct segment_command {
  uint32_t  cmd;
  uint32_t  cmdsize;
  char      segname[16];
  uint32_t  vmaddr;
  uint32_t  vmsize;
  uint32_t  fileoff;
  uint32_t  filesize;
  vm_prot_t maxprot;
  vm_prot_t initprot;
  uint32_t  nsects;
  uint32_t  flags;
};

查阅 Load command 1 这么些有些可以找到 initprot r-x ,表示只读和可进行。

在加载命令里或者看看 __TEXT __text 的section的内容

Section
  sectname __text
   segname __TEXT
      addr 0x0000000100000f50
      size 0x0000000000000034
    offset 3920
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0

addr
的值表示代码的职分地址,在地点反汇编的代码里可以看看地方是相同的,offset
表示在文书中的偏移量。

单个文件的就这么了,然则工程都是三个源文件的,那么几个文件是怎么合成一个可执行文件的啊?那么建三个文本来看看先。
Foo.h

#import <Foundation/Foundation.h>

@interface Foo : NSObject

- (void)say;

@end

Foo.m

#import "Foo.h"

@implementation Foo

- (void)say
{
    NSLog(@"hi there again!\n");
}

@end

SayHi.m

#import "Foo.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        Foo *foo = [[Foo alloc] init];
        [foo say];
        return 0;
    }
}

先编译多少个文本

xcrun clang -c Foo.m
xcrun clang -c SayHi.m

再将编译后的文件链接起来,这样就可以生成 a.out 可执行文件了。

xcrun clang SayHi.o Foo.o -Wl,`xcrun --show-sdk-path`/System/Library/Frameworks/Foundation.framework/Foundation

(5)

逆向 Mach-O 文件

必要先安装 tweak,安装越狱能够经过 cydia,不越狱直接打包成 ipa
安装包。越狱的话会设置一个 mobilesubstrate 的动态库,使用 theos
开发工具,非越狱的直接把这几个库打包进 ipa 中要么直接改动汇编代码。

Mobilesubstrate 提供了多个模块来方便开发。

  • MobileHooker:利用 method swizzling
    技术定义一些宏和函数来替换系统或者目的函数。
  • MobileLoader:在程序启动时将大家写的破解程序用的第三方库注入进来。怎么注入的吧,还记得往日说的
    clang attribute 里的一个 attribute((constructor)) 么,它会在 main
    执行从前实施,所以把大家的 hook 放在那边就足以了。
  • Safe mode:类似安全格局,会禁用的转移。

原先波及 Mach-O 的结构有 Header,Load commands 和 Data,Mobileloader
会通过改动二进制的 loadCommands
来先把自己注入然后再把大家写的第三方库注入进来,那样破解程序就会放在
Load commands 段里面了。

自然假使是大家团结的次序大家是明亮要替换哪些措施的,既然是逆向肯定是人家的程序了,这一个时候就要求去先分析下我们想替换方法是哪个,互联网有关的剖析可以用常用这一个抓包工具,比如
查理,WireShark 等,静态的可以通过砸壳,反汇编,classdump
头文件来分析 app 的架构,对应的常用工具dumpdecrypted,hopper
disassembler 和
class_dump。运行时的辨析可用工具有运行时控制台cycript,远程断点调试lldb+debugserver,logify。

自身本来打算等落樱有雪能真正适应那片江湖后再离开,但我没悟出分别来的这么匆忙。

dyld动态链接

扭转可执行文件后就是在启动时举行动态链接了,举办标记和地点的绑定。首先会加载所依靠的
dylibs,勘误地址偏移,因为 iOS 会用 ASLR 来做地点偏移防止攻击,确定
Non-Lazy Pointer 地址进行标记地址绑定,加载所有类,最终执行 load 方法和
clang attribute 的 constructor 修饰函数。

用先前 Mach-O
章节的例证继续分析,每个函数,全局变量和类都是透过标志的款式来定义和运用的,当把对象文件链接成一个推行文书时,链接器在对象文件和动态库之间对符号做分析处理。

标记表会规定它们的标记,使用 nm 工具看看

xcrun nm -nm SayHi.o

                 (undefined) external _OBJC_CLASS_$_Foo
                 (undefined) external _objc_autoreleasePoolPop
                 (undefined) external _objc_autoreleasePoolPush
                 (undefined) external _objc_msgSend
0000000000000000 (__TEXT,__text) external _main
  • OBJC_CLASS$_Foo 表示 Foo 的 OC 符号。
  • (undefined) external 表示未落到实处非私有,若是是私家就是 non-external。
  • external _main 表示 main() 函数,处理 0 地址,将要到
    __TEXT,__text section

再看看 Foo

xcrun nm -nm Foo.o

                 (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 -[Foo say]
0000000000000060 (__DATA,__objc_const) non-external l_OBJC_METACLASS_RO_$_Foo
00000000000000a8 (__DATA,__objc_const) non-external l_OBJC_$_INSTANCE_METHODS_Foo
00000000000000c8 (__DATA,__objc_const) non-external l_OBJC_CLASS_RO_$_Foo
0000000000000110 (__DATA,__objc_data) external _OBJC_METACLASS_$_Foo
0000000000000138 (__DATA,__objc_data) external _OBJC_CLASS_$_Foo

因为 undefined 符号表示该公文类未落实的,所以在对象文件和 Fundation
framework 动态库做链接处理时,链接器会尝试解析所有的 undefined 符号。

链接器通过动态库解析成符号会记录是透过哪个动态库解析的,路径也会一起记录。比较下
a.out 符号表看看是怎么解析符号的。

xcrun nm -nm a.out

                 (undefined) external _NSLog (from Foundation)
                 (undefined) external _OBJC_CLASS_$_NSObject (from CoreFoundation)
                 (undefined) external _OBJC_METACLASS_$_NSObject (from CoreFoundation)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external __objc_empty_cache (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
0000000100000e90 (__TEXT,__text) external _main
0000000100000f10 (__TEXT,__text) non-external -[Foo say]
0000000100001130 (__DATA,__objc_data) external _OBJC_METACLASS_$_Foo
0000000100001158 (__DATA,__objc_data) external _OBJC_CLASS_$_Foo

看看哪些 undefined 的记号,有了越来越多音讯,可以领略在哪个动态库可以找到。

透过 otool 可以找到所需库在哪

xcrun otool -L a.out

a.out:
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1349.25.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1348.28.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

libSystem 里有无数我们耳熟能详的lib

  • libdispatch:GCD
  • libsystem_c:C语言库
  • libsystem_blocks:Block
  • libcommonCrypto:加密,比如md5

dylib
这种格式的表示是动态链接的,编译的时候不会被编译到执行文书中,在程序执行的时候才
link,那样就毫无算到包的分寸里,而且也可以不立异实施顺序就可见更新库。

打印什么库被加载了

(export DYLD_PRINT_LIBRARIES=; ./a.out )

dyld: loaded: /Users/didi/Downloads/./a.out
dyld: loaded: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
dyld: loaded: /usr/lib/libSystem.B.dylib
dyld: loaded: /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
...

数数还挺多的,因为 Fundation
还会借助一些别样的动态库,其余的库还会再借助越多的库,那样相互依赖的标志会过多,要求处理的岁月也会相比较长,那里系统上的动态链接器会利用共享缓存,共享缓存在
/var/db/dyld/。当加载 Mach-O
文件时动态链接器会先反省共享内存是不是有。每个进度都会在投机地址空间映射那几个共享缓存,那样可以优化启动速度。

动态链接器的意义顺序是何等的吧,可以先看看 迈克 Ash 写的那篇有关 dyld
的博客: Dynamic Linking On OS
X

dyld 做了些什么事

  • kernel 做启动程序初步准备,开首由dyld负责。
  • 基于十分不难的原始栈为 kernel 设置进度来启动自身。
  • 利用共享缓存来拍卖递归依赖带来的属性难点,ImageLoader
    会读取二进制文件,其中蕴蓄了大家的类,方法等各样符号。
  • 旋即绑定 non-lazy 的标志并设置用于 lazy bind 的需要表,将这个库 link
    到实践文书里。
  • 为可执行文件运行静态早先化。
  • 安装参数到可执行文件的 main 函数并调用它。
  • 在执行时期,通过绑定符号处理对 lazily-bound 符号存根的调用提供
    runtime 动态加载服务(通过 dl*() 这些 API
    ),并为gdb和其他调试器提供钩子以博取重大音讯。runtime 会调用
    map_images 做分析和处理,load_images 来调用 call_load_methods
    方法遍历所有加载了的 Class,依据持续层级依次调用 +load 方法。
  • 在 mian 函数重回后运行 static terminator。
  • 在一些意况下,一旦 main 函数再次来到,就必要调用 libSystem 的 _exit。

查阅运行时的调用 map_images 和 调用 +load 方法的连锁 runtime
处理可以因此 RetVal 的可debug 的 objc/runtime RetVal/objc-runtime: objc
runtime
706

来展开断点查看调用的 runtime 方法具体落成。在 debug-objc
下开创一个类,在 +load 方法里断点查看走到那边调用的库房如下:

0  +[someclass load]
1  call_class_loads()
2  ::call_load_methods
3  ::load_images(const char *path __unused, const struct mach_header *mh)
4  dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*)
11 _dyld_start

在 load_images 方法里断点 p path
可以打印出具有加载的动态链接库,这些办法的 hasLoadMethods
用于火速判断是不是有 +load 方法。

prepare_load_methods 那个方法会获取所有类的列表然后收集其中的 +load
方法,在代码里能够发现 Class 的 +load 是先进行的,然后实施 Category
的。为啥这么做,原因可以透过 prepare_load_methods
那一个措施来看,在遍历 Class 的 +load 方法时会执行 schedule_class_load
这些方法,那一个方法会递归到根节点来满足 Class 收集完整关系树的需求。

最后 call_load_methods 会创制一个 autoreleasePool
使用函数指针来动态调用类和 Category 的 +load 方法。

只要想打听 Cocoa 的 Fundation 库可以因此
GNUStep
源码来上学。比如 NSNotificationCenter 发送布告是按什么顺序发送的可以查阅
NSNotificationCenter.m 里的 addObserver 方法和 postNotification
方法,看看观望者是怎么添加的和怎么被遍历布告到的。

dyld 是开源的: GitHub –
opensource-apple/dyld

还足以看看苹果的 WWDC 视频 WWDC 2016 Session
406

里教授对启动开展优化。

那篇小说也不利: Dynamic Linking of Imported Functions in Mach-O –
CodeProject

本次未来,我等了她一个月他都并未上线。我驾驭不可以再等她了。

附:安装编译 LLVM

自家给她写了一封长长的信,想来下次上线的时候他必然会看出。

二种得到形式

先下载 LLVM
svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

在 LLVM 的 tools 目录下下载 Clang
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

在 LLVM 的 projects 目录下下载 compiler-rt,libcxx,libcxxabi
cd ../projects
svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
svn co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx
svn co http://llvm.org/svn/llvm-project/libcxxabi/trunk libcxxabi

在 Clang 的 tools 下安装 extra 工具
cd ../tools/clang/tools
svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
  • git

git clone http://llvm.org/git/llvm.git
cd llvm/tools
git clone http://llvm.org/git/clang.git
cd ../projects
git clone http://llvm.org/git/compiler-rt.git
cd ../tools/clang/tools
git clone http://llvm.org/git/clang-tools-extra.git

自己去找了七宝丸研商,这几回我没留手,尽管用的是剑,但出招的品格是本人在当杀手“没熟的烧麦”时的作风。

安装

brew install gcc
brew install cmake
mkdir build
cd build
cmake /path/to/llvm/source
cmake --build .

如果希望是 xcodeproject 方式 build 可以使用 -GXcode
mkdir xcodeBuild
cd xcodeBuild
cmake cmake -GXcode /path/to/llvm/source

接下去可以看看 LLVM 的官方教程怎么着贯彻一个友好的编程语Kaleidoscope。
LLVM Tutorial: Table of Contents — LLVM 5
documentation

此处有个使用swift的贯彻的延续串教程
Building a Compiler in Swift with
LLVM

七宝丸输了,但她赢了前途。

附:其余编译工具

他的剑是切磋比武中练就出去的剑,铁面无私,遇强则强,光以剑法而论,失利的本人很难克制每一天都在上扬的她,她的剑是天赋之剑。

js写的C++解释器JSCPP

顺应学生攻读时亦可有利于的在浏览器里直接编c++程序。项目地址:GitHub –
felixhao28/JSCPP: A simple C++ interpreter written in
JavaScript

可自我的剑是刀光血雨中练就的剑,所以剑下阴毒,我的剑也是众数十次为了守护背后的帮会和挚爱的亲友而出的剑,所以坚毅果决,我的剑又见证了人世中过多的盛衰,见证了比比皆是的相逢与离别,所以它又是江湖之剑。而下方之剑,在自身急需它的时候平素没让自家失望。

附:资料网址

(6)

在七宝丸那装完末了一次逼,我又赶到了空雾峰头。

本身曾在那和一弦清歌饮酒论剑,当年他一袭红衣似火,我一衫白袍如月。现在我们在此话别。

自己问他,现在还有忙着去相亲么?

他点了点头,笑着说起协调多年来接近的阅历。我还记得那时的他对相亲之事反感莫名,也不知道为啥近期就能笑对。

“不可以,玩的是玩玩,但人生也要过啊~!”

她也说自家此前的时候动不动就拔剑相向,但现行那片江湖可还有那么多让自己拔剑的说辞?

自我想了想,好像是的,除了最终以人间之剑教七宝丸一些征战经验,如同再没有怎么理由能让自身实在的拔剑。

清歌笑我还蛮中二的,我说我身是老人身,心仍旧少年心。

我要走的时候问清歌,将来会不会还有机会共同喝酒赏月?

清歌想了漫长说:“本来想说点如意的话,但想了想应该不会再有。”

自己点了点头:“嗯,我想也是如此,那么就此别过。”

“就此别过。”

妙龄子弟江湖老,老时何必恋旧人。

(7)

那年夏季,我算是去了他所在的都会,走过他来时的路。

汤圆要热乎的时候吃才最好,她的都会要夏季来才比较美。

自身没有去找他,我只是在他的都会逗留许久,在离开的前夕,我漫步在江边。

国外的江天,夜色尚未深沉,却已有几颗星星出现在了东方的天际,繁星和江水,野草和烟火,这似乎注定成为自我春日最后的想起。

烟火响起的时候,我恍然无比的怀想落樱有雪,想念和她还有清歌、七宝丸一起渡过的光阴,在这欢笑声的人流中,在这春天的微风里。

自身在天边看来了他,她旁边走着其余一个男生,几个人有说有笑的旗帜。

自己还在远方看来了其余一个小女孩,白色的裙子,扎着轻盈的双马尾,让自己纪念了小樱雪,只是不知缘何这个女孩神色有些孤寂。

烟花甘休,星辰正在夏天的夜空中冉冉升起,晚风轻轻的晃动着树枝,远处的街市华灯初上,路上出来走走的乘客逐步多了起来,其实自己曾经知道游戏外又是另一个社会风气。

只是他也早已对自身说过:少年如星似月,少女如樱似雪,人在最美好的年华就应有去完结最美好的故事,去相信最美好的道理,去拥有最难得的善良。

那么,到底该去做到美好的故事?仍旧该去拥有体贴的善良呢?

那一天,我最后转身离开,像个将要老去的少年。

愿你能幸福的生活下去,嗯,就当那份祝愿就是自个儿少年心头最终的星月。

——————————————————————————————————————————

这篇故事是《暗恋抄·少年如星五月,少女如樱似月》的林某扬番外。那篇故事原文本来就写得很别扭,有明暗两条故事线,所以我尽可能补充部分番外,让任何故事更完美些呢。

初稿链接:暗恋抄·少年如星似月,少女如樱似雪

首先个番外:暗恋抄·星月樱雪外传·江湖往事

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图