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

前言

2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at
Urbana-Champaign 简称UIUC)这所独具世界声誉的世界级公立研究型大学之 Chris
Lattner(他的 twitter
@clattner_llvm
) 开发了一个深受作 Low Level Virtual Machine
的编译器开发工具套件,后来涉嫌范围更加不行,可以用于常规编译器,JIT编译器,汇编器,调试器,静态分析工具等一律层层和编程语言相关的工作,于是便将简称
LLVM 这个简称作为了正规化的讳。Chris Lattner 后来同时开发了 Clang,使得
LLVM 直接挑战 GCC 的地位。2012年,LLVM 获得美国计算机学会 ACM
的软件系统大奖,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齐。

Chris Lattner 生于 1978 年,2005年入苹果,将苹果采用的 GCC 全面转为
LLVM。2010年初始着力开发 Swift 语言。

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

swift 是 Swift / LLVM,其中 Swift 前端会多来 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,这个接口类似 RecursiveASTVisitor。

浑 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 相关文档

  • http://clang.llvm.org/docs/Tooling.html
  • http://clang.llvm.org/docs/IntroductionToTheClangAST.html
  • http://clang.llvm.org/docs/RAVFrontendAction.html
  • http://clang.llvm.org/docs/LibTooling.html
  • http://clang.llvm.org/docs/LibASTMatchers.html

自我眼前的那么把剑叫做“江湖老”,取“少年子弟江湖老”之完全,曾是当年名动一时的稀有宝剑。

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 。

随即三个方案作者都分别写了文章详细说明该实现方案。

  • JSPatch实现原理详解 « bang’s
    blog
  • DynamicCocoa:滴滴 iOS
    动态化方案的出世与起航
  • OCS——史上最好疯之iOS动态化方案 –
    简书

滴滴的王康在做瘦身时也促成了一个自定义的 clang
插件,具体于定义插件的兑现可查看他的当下文章
《基于clang插件的同种iOS包大小瘦身方案》

那么我们而团结动手做该怎么入门呢,除了我带来的范例外还起来教程可以望。

  • 采集一些哪采取 clang 库的例证:GitHub – loarabia/Clang-tutorial:
    A collection of code samples showing usage of clang and llvm as a
    library
  • 当 Xcode 中上加 clang 静态分析由定义 checks: Running the analyzer
    within
    Xcode
  • 将 LLVM C 的 API 用 swift 来包装: GitHub –
    harlanhaskins/LLVMSwift: A Swifty wrapper for the LLVM C API version
    3.9.1

清歌和本人平都是老江湖了,我们一并纵剑江湖之时节,现在游玩里的大多数人还无知道当哪。

编译后生成的二进制内容 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 段里面了。

当然如果是我们和好之次第我们是喻要替换哪些方法的,既然是逆向肯定是别人的顺序了,这个上就得去先分析下我们想替换方法是谁,网络有关的分析好据此常用那些抓包工具,比如
Charles,WireShark 等,静态的好经砸壳,反汇编,classdump
头文件来分析 app 的架,对应的常用工具dumpdecrypted,hopper
disassembler 和
class_dump。运行时的剖析可用工具有运行时操台cycript,远程断点调试lldb+debugserver,logify。

  • 此间有个实例,讲解如何通过逆向实现微信抢红包的插件: 【Dev Club
    分享第三欲】iOS 黑客技术大揭秘 – DEV
    CLUB
  • 入门文章好望就首: MyArticles/iOS冰及火之歌 at master ·
    zhengmin1989/MyArticles ·
    GitHub
  • 戏来新花样: 私自科技:把第三正在 iOS 应用转成为动态库 – Jun’s
    Blog,作者另一样首文章:
    iOS符号表恢复&逆向出宝 – Jun’s
    Blog

自本打算等获得樱有雪能真正适应当时片江湖后再也去,但自我从没悟出分别来的这么匆忙。

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
文件时动态链接器会先行检查共享内存是否发生。每个过程都见面以温馨地址空间映射这些共享缓存,这样可优化启动速度。

动态链接器的意顺序是什么样的呢,可以事先看 Mike 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

自让它写了相同查封长长的迷信,想来下次上线的早晚它定会见到。

多取得方式

  • 官网:http://releases.llvm.org/download.html
  • svn

先下载 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

唯独自己的剑是刀光血雨中练就的宝剑,所以剑下无情,我的剑为是无数蹩脚为守护背后的帮会和友爱的亲朋而起之宝剑,所以坚毅果决,我的剑而见证了人世中很多的盛衰,见证了重重之逢与离别,所以她而是凡的剑。而下方的剑,在自我用它们的时光向没有让自己失望。

比:资料网址

  • http://llvm.org
  • http://clang.llvm.org/
  • http://www.aosabook.org/en/llvm.html
  • GitHub – loarabia/Clang-tutorial: A collection of code samples
    showing usage of clang and llvm as a
    library
  • Using an external Xcode Clang Static Analyzer binary, with
    additional checks – Stack
    Overflow

(6)

以七宝丸那么装完最后一赖逼,我还要赶到了空雾峰头。

自家曾经于当下同均等弦清歌饮酒论剑,当年其一样传承红衣似火,我一样上身白袍如月。现在我们在是话别。

本人问话其,现在还有忙在去相亲么?

它接触了碰头,笑着说从好近年来近的经验。我还记当时底它们对准如胶似漆的业反感莫名,也非掌握干什么如今即令能乐对。

“没道,玩的是打,但人生呢如过啊~!”

其吗说我以前的时段动不动就拔剑相向,但本及时片江湖但是还有那基本上吃自家拔剑的理?

自思了纪念,好像对,除了最后因凡的剑教七宝丸一些交战更,似乎还无呀说辞能被我实在的拔剑。

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

自己一旦动之早晚问清歌,以后会不见面还有机会同台喝赏月?

清歌想了绵绵说:“本来想说点如意的话,但想了纪念当无会见再也出。”

自家沾了接触头:“嗯,我怀念啊是如此,那么即便这变化了。”

“就以此变化了。”

少年子弟江湖老,老时何必恋旧人口。

(7)

那年夏日,我好不容易去矣它所于的城市,走过她来常常之行程。

汤圆要热的时节吃才最好,她的都会如果夏天来才于美。

我未曾失去探寻她,我只是以它们的市待许久,在离开的前夕,我漫步于江边。

天涯的江天,夜色尚未深沉,却都起几乎粒星星出现于了东边的天际,繁星和江水,野草和烟火,这犹如注定成为自己夏天最后的想起。

烟火响起的上,我猛然无比的感念落樱有洗,想念和她还有清歌、七宝丸一起过的生活,在那么欢笑声的人流吃,在那夏天的轻风里。

自家于天边看了其,她边上走在另外一个男生,两丁产生说有笑的榜样。

我还当天看来了另外一个稍微女孩,白色的裙子,扎在轻盈的双马尾,让自家想起了有点樱雪,只是不知何故好女孩神色有些孤寂。

烟花结束,星辰正在夏季底夜空被冉冉升起,晚风轻轻的忽悠着树枝,远处的街市华灯初上,路上下走走的客渐渐多了起来,其实自己曾经知道游戏外还要是别一个世界。

一味是其也早已对自身说罢:少年如星辰似月,少女如樱似雪,人于尽美好的年纪虽该去好最好美好的故事,去相信最美好的道理,去有最珍奇的善。

那么,到底该去就美好的故事?还是拖欠去有珍贵的臧呢?

那么同样龙,我最后转身离开,像个将要老去的豆蔻年华。

愿意君能甜之在下去,嗯,就当就卖祝愿就是自己少年心头最后之星月。

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

即首故事是《暗恋抄·少年如星辰四月,少女如樱似月》的林某扬番外。这首故事原文本来就是形容得死去活来隐晦,有明暗两长达故事线,所以自己竭尽补充部分旗外,让漫天故事重复周全些吧。

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

第一独番外:暗恋抄·星月樱雪外传·江湖往事

发表评论

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

网站地图xml地图