-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
1823 lines (1423 loc) · 450 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.3.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"example.com","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
</script>
<meta name="description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
<meta property="og:type" content="website">
<meta property="og:title" content="牛易疯先森的开发记录">
<meta property="og:url" content="http://example.com/index.html">
<meta property="og:site_name" content="牛易疯先森的开发记录">
<meta property="og:description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="joakim.liu">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="http://example.com/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'zh-CN'
};
</script>
<title>牛易疯先森的开发记录</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">牛易疯先森的开发记录</h1>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2022/09/30/stack/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="joakim.liu">
<meta itemprop="description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="牛易疯先森的开发记录">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2022/09/30/stack/" class="post-title-link" itemprop="url">iOS 函数调用栈那些事</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2022-09-30 08:26:20" itemprop="dateCreated datePublished" datetime="2022-09-30T08:26:20+08:00">2022-09-30</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>《<a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a>》 <code>10.2 栈与调用惯例</code> 里面讲到了堆栈帧这块的内容,联想到同事面试时说到捕获奔溃调用栈的问题,感觉挺有意思,于是记录一下。<br>本文主要讲解以下内容</p>
<ol>
<li>栈帧是什么东西?</li>
<li>Arm64 汇编基础,Arm64 栈帧</li>
<li>栈帧回溯怎么玩?怎么符号化?常见的三方库又是怎么玩的?</li>
</ol>
<h1 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h1><p>《<a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a>》是这么描述栈的:</p>
<blockquote>
<p>栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今能够看见的所有的计算机语言。<br>在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO),多多少少像叠成一叠的书:先叠上去的书在最下面,因此要最后才能取出。<br>在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使栈减小。</p>
</blockquote>
<h2 id="栈帧"><a href="#栈帧" class="headerlink" title="栈帧"></a>栈帧</h2><p>栈保存了函数调用所需的信息,而这些信息称为堆栈帧(Stack Frame)或活动记录(Activate Record),包括以下内容:</p>
<ul>
<li>函数的返回地址和参数</li>
<li>临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量</li>
<li>保存的上下文:包括在函数调用前后需要保持不变的寄存器</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/stack/20220930091349.png" alt="栈帧"></p>
<p>上图是 i386 中一个函数的栈帧,根据 ebp(Frame Pointer, 帧寄存器) 和 esp(Stack Pointer, 栈寄存器) 这两寄存器可以划分一个函数的堆栈。</p>
<ol>
<li>ebp 是固定的,始终指向栈底(高地址)</li>
<li>esp 始终指向栈顶(低地址),它是动态变化的</li>
<li>可以通过 ebp esp 和获取栈帧中的数据,比如,ebp+4 获取函数的返回地址</li>
</ol>
<h2 id="调用惯例"><a href="#调用惯例" class="headerlink" title="调用惯例"></a>调用惯例</h2><p>调用惯例:函数的调用方和被调用方对于函数如何调用须要有一个明确的约定。就像两个人沟通一样的,只有使用相同的语言(普通话)才能进行友好的沟通。<br>而调用惯例包括以下几方面的内容:</p>
<ol>
<li>函数参数的传递顺序和方式<ol>
<li>函数参数的传递有很多种方式,最常见的一种是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序:是从左至右,还是从右至左。有些调用惯例还允许使用寄存器传递参数,以提高性能。</li>
</ol>
</li>
<li>栈的维护方式<ol>
<li>在函数将参数压栈之后,函数体会被调用,此后需要将被压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由函数的调用方来完成,也可以由函数本身来完成。 // PS: 如果不进行栈回退 pop 操作的话,函数调用会将程序的栈空间用完,从而出现经典的 stackoverflow 错误</li>
</ol>
</li>
<li>名字修饰(Name-mangling)的策略<ol>
<li>为了链接的时候对调用惯例进行区分,调用管理要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略。</li>
</ol>
</li>
</ol>
<h1 id="Arm64"><a href="#Arm64" class="headerlink" title="Arm64"></a>Arm64</h1><p>有了上面书本基础知识的加持,现在到 Arm64 架构下实践一波吧。</p>
<h2 id="汇编基础"><a href="#汇编基础" class="headerlink" title="汇编基础"></a>汇编基础</h2><h3 id="寄存器"><a href="#寄存器" class="headerlink" title="寄存器"></a>寄存器</h3><p><a href="https://zh.wikipedia.org/wiki/%E5%AF%84%E5%AD%98%E5%99%A8">寄存器</a>(Register)是中央处理器内用来暂存指令、数据和地址的电脑存储器。 它就是 CPU 内部用来存储数据的存储器件,容量小、访问速度快。<br><br/></p>
<p>而 Arm64 有 31 个通用寄存器,相关用途如下。 </p>
<blockquote>
<p>The 64-bit ARM (AArch64) calling convention allocates the 31 general-purpose registers as:<br>x31 (SP): Stack pointer or a zero register, depending on context.<br>x30 (LR): Procedure link register, used to return from subroutines.<br>x29 (FP): Frame pointer.<br>x19 to x29: Callee-saved.<br>x18 (PR): Platform register. Used for some operating-system-specific special purpose, or an additional caller-saved register.<br>x16 (IP0) and x17 (IP1): Intra-Procedure-call scratch registers.<br>x9 to x15: Local variables, caller saved.<br>x8 (XR): Indirect return value address.<br>x0 to x7: Argument values passed to and results returned from a subroutine.</p>
</blockquote>
<p>摘自 <a href="https://en.wikipedia.org/wiki/Calling_convention#ARM_(A64)">Calling_convention#ARM_(A64)</a>。</p>
<br/>
<p>总结下常用的:</p>
<ol>
<li>x0-x7: 用来存放函数调用的参数和函数返回值(x0),更多参数可以使用堆栈来存放</li>
<li>x29 (FP): 上图 i386 中的 ebp, 它里面存储的是上一个函数(该函数的调用方 caller) ebp 的地址</li>
<li>x30 (LR): 存储函数的返回地址</li>
<li>SP: 上图 i386 中的 esp, 指向栈顶<ol>
<li>现在执行 lldb 命令<code>register read x31</code> 会报错:<code>error: Invalid register name 'x31'.</code></li>
</ol>
</li>
<li>PC: 记录 CPU 当前执行的是哪条指令</li>
</ol>
<h3 id="指令"><a href="#指令" class="headerlink" title="指令"></a>指令</h3><p>一些常见的指令,内容摘自 <a href="https://blackteachinese.github.io/2017/07/12/arm64/">10分钟入门arm64汇编</a>,具体的可以参加 ARM 操作手册。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">add x0,x0,#1 ;x0 <==x0+1 ,把x0的内容加1。</span><br><span class="line">add x0,x0,#0x30 ;x0 <==x0+0x30,把x0的内容加 0x30。</span><br><span class="line">add x0,x1,x3 ;x0 <==x1+x3, 把x1的内容加上x3的内容放入x0</span><br><span class="line">add x0,x1,x3,lsl #3 ;x0 <==x0+x3*8 ,x3的值左移3位就是乘以8,结果与x1的值相, 放入x0.</span><br><span class="line">add x0,x1,[x2] ;x0 <==x1+[x2], 把x1的内容加上x2的内容作为地址取内存内容放入x0</span><br><span class="line">ldr x0,[x1] ;x0 <==[x1], 把x1的内容作为地址取内存内容放入x0</span><br><span class="line">str x0,[x1] ;[x1] <== x0, 把x0的内容放入x1的内容作为地址的内存中</span><br><span class="line">ldr x0,[x1,#4] ;x0 <==[x1+4], 把x1的内容加上4, 作为内存地址, 取其内容放入x0</span><br><span class="line">ldr x0,[x1,#4]! ;x0 <==[x1+4]、 x1<==x1+4, 把x1的内容加上4, 作为内存地址, 取其内容放入x0, 然后把x1的内容加上4放入x1</span><br><span class="line">ldr x0,[x1],#4 ;x0 <==[x1] 、x1 <==x1+4, 把x1的内容作为内存地址取内存内容放入x0, 并把x1的内容加上4放入x1</span><br><span class="line">ldr x0,[x1,x2] ;x0 <==[x1+x2], 把x1和x2的内容相加, 作为内存地址取内存内容放入x0</span><br></pre></td></tr></table></figure>
<h2 id="栈帧-1"><a href="#栈帧-1" class="headerlink" title="栈帧"></a>栈帧</h2><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/stack/aapcs64-variadic-stack.png" alt="aapcs64-variadic-stack"><br>跟《<a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a>》里面的栈帧差不多,图片来自 <a href="https://github.com/ARM-software/abi-aa/blob/60a8eb8c55e999d74dac5e368fc9d7e36e38dda4/aapcs64/aapcs64.rst#the-stack">Procedure Call Standard for the Arm® 64-bit Architecture (AArch64)</a>。</p>
<h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p>用一个 Demo 将上面的内容串联起来。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (int)test1:(int)a {</span><br><span class="line"> int res = [self test2:a b:2];</span><br><span class="line"> return res;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (int)test2:(int)a b:(int)b {</span><br><span class="line"> int res = a + b;</span><br><span class="line"> return res;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (void)viewDidLoad {</span><br><span class="line"> [super viewDidLoad];</span><br><span class="line"> </span><br><span class="line"> int res = [self test1:1];</span><br><span class="line"> NSLog(@"res: %d", res);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在函数调用方 <code>test1</code> 和被调方 <code>test2</code> 的第一行代码处下断点,并用汇编查看。</p>
<h3 id="caller-test1"><a href="#caller-test1" class="headerlink" title="caller - test1"></a>caller - test1</h3><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/stack/20220930104416.png"></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Demo_fishhook`-[ViewController test1:]:</span><br><span class="line"> 0x102067294 <+0>: sub sp, sp, #0x30 ; sp = sp - 0x30(48), 开辟栈空间</span><br><span class="line"> 0x102067298 <+4>: stp x29, x30, [sp, #0x20] ; 将 x29(fp) 里面的内容存入 sp+0x20(32)的位置,占 8B(x29 共 64bit, 8B) 长度;将 x30(lr) 的内容存入 sp+0x20+8=sp+40 的位置,占 8B</span><br><span class="line"> 0x10206729c <+8>: add x29, sp, #0x20 ; x29 = sp + 0x20</span><br><span class="line"> 0x1020672a0 <+12>: stur x0, [x29, #-0x8] ; 将 x0 的内容,x29-0x8 的位置</span><br><span class="line"> 0x1020672a4 <+16>: str x1, [sp, #0x10] ; 将 x1 的内容放入 sp+0x10 的位置</span><br><span class="line"> 0x1020672a8 <+20>: str w2, [sp, #0xc] ; w2 是 x2 的低32位,占 4B</span><br><span class="line">-> 0x1020672ac <+24>: ldur x0, [x29, #-0x8] ; 数据读取,就是将上面的数据再读出来</span><br><span class="line"> 0x1020672b0 <+28>: ldr w2, [sp, #0xc] ; 数据读取</span><br><span class="line"> 0x1020672b4 <+32>: adrp x8, 87</span><br><span class="line"> 0x1020672b8 <+36>: ldr x1, [x8, #0xb80] ; 数据读取放入 x1 寄存器中</span><br><span class="line"> 0x1020672bc <+40>: mov w3, #0x2 ; x3 的低32位存的值是 0x2</span><br><span class="line"> 0x1020672c0 <+44>: bl 0x102d2f0e4 ; symbol stub for: objc_msgSend ; 函数跳转</span><br><span class="line"> 0x1020672c4 <+48>: str w0, [sp, #0x8] ; 存储 test2 的结果</span><br><span class="line"> 0x1020672c8 <+52>: ldr w0, [sp, #0x8] ; 读取 w0 </span><br><span class="line"> 0x1020672cc <+56>: ldp x29, x30, [sp, #0x20] ; 将 [sp, #0x20] 的内容放入 x29 x30 寄存器</span><br><span class="line"> 0x1020672d0 <+60>: add sp, sp, #0x30 ; sp = sp + 0x30</span><br><span class="line"> 0x1020672d4 <+64>: ret </span><br></pre></td></tr></table></figure>
<br/>
<p>这里大概做了下面几件事</p>
<ol>
<li>开辟栈空间,存储 x29 x30 的值到栈,因为后面会修改 x29 x30 的值</li>
<li>给 x29 赋值,即固定当前函数 x29 的位置</li>
<li>存储和读取 x0 x1 w2<code>test2:b:</code> 函数入参的值</li>
<li>给 x3 赋值为 2, 相当于 <code>test2:b:</code> 函数入参 b 的值</li>
<li><code>bl</code> 调用 <code>test2:b:</code> 函数</li>
<li>从栈中取出 x29 x30 的值,回退栈空间</li>
<li>ret: 函数返回到 x30 所指向的地址</li>
</ol>
<br/>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">(lldb) register read</span><br><span class="line">General Purpose Registers:</span><br><span class="line"> x0 = 0x0000000102709160</span><br><span class="line"> x1 = 0x00000001020ab733 "test1:"</span><br><span class="line"> x2 = 0x0000000000000001</span><br><span class="line"> x3 = 0x000000016dd9dbf0</span><br><span class="line"> x4 = 0x0000000000000010</span><br><span class="line"> x5 = 0x0000000000000020</span><br><span class="line"> x6 = 0x000000016dd9d8f0</span><br><span class="line"> x7 = 0x0000000000000000</span><br><span class="line"> x8 = 0x00000001020be000 (void *)0x00000001020ab182: initWithCodeType:baseAddress:size:name:uuid:.hex + 7651</span><br><span class="line"> x9 = 0x0000000000000000</span><br><span class="line"> x10 = 0x000000000000005d</span><br><span class="line"> x11 = 0x00000001030221d8</span><br><span class="line"> x12 = 0x000000000000005d</span><br><span class="line"> x13 = 0x0000000000000000</span><br><span class="line"> x14 = 0x0000000180964000</span><br><span class="line"> x15 = 0x000000020b12c000</span><br><span class="line"> x16 = 0x00000001020bf9b2 (void *)0xe12800000001020b</span><br><span class="line"> x17 = 0x0000000102067294 Demo_fishhook`-[ViewController test1:] at ViewController.m:144</span><br><span class="line"> x18 = 0x0000000000000000</span><br><span class="line"> x19 = 0x0000000102709160</span><br><span class="line"> x20 = 0x0000000000000000</span><br><span class="line"> x21 = 0x00000001f59e3000 UIKitCore`_UIInternalPreference_IdleSchedulerTargetDeadlineFraction</span><br><span class="line"> x22 = 0x000000019b10ec13 </span><br><span class="line"> x23 = 0x000000019b7043f5 </span><br><span class="line"> x24 = 0x0000000000000000</span><br><span class="line"> x25 = 0x00000001f630c000 UIKitCore`_UIPreviewPresentationAnimator._startMediaTime</span><br><span class="line"> x26 = 0x00000001027083f0</span><br><span class="line"> x27 = 0x000000019bb48a24 </span><br><span class="line"> x28 = 0x00000001fab5f4e8 CoreFoundation`__NSArray0__struct</span><br><span class="line"> fp = 0x000000016dd9da20</span><br><span class="line"> lr = 0x000000010206718c Demo_fishhook`-[ViewController viewDidLoad] + 76 at ViewController.m:84:9</span><br><span class="line"> sp = 0x000000016dd9da00</span><br><span class="line"> pc = 0x00000001020672ac Demo_fishhook`-[ViewController test1:] + 24 at ViewController.m:145:16</span><br><span class="line"> cpsr = 0x40000000</span><br></pre></td></tr></table></figure>
<p>现在来 debug 看下相关寄存器里面的值,分别看.</p>
<ul>
<li>x0-x7, 存放参数和返回值<ul>
<li>x0 和 x1 存储的是 OC 方法的前两个隐藏入参: self 和 _cmd. <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(lldb) po 0x0000000102709160</span><br><span class="line"><ViewController: 0x102709160></span><br><span class="line"></span><br><span class="line">(lldb) po (char *)0x00000001020ab733</span><br><span class="line">"test2:b:" </span><br></pre></td></tr></table></figure></li>
<li> <code>x2 = 0x0000000000000001</code> 存储的值是 1, 主上面的汇编代码使用的 w2, 即 x2 低 32 位(即 000000001), 就是值 1 </li>
<li> 断点指向到 0x1020672c0 处后,<code>x3 = 0x0000000000000002</code>,同 x2, 值 2</li>
</ul>
</li>
<li>pc, 当前断点指向的地址</li>
</ul>
<br/>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x000000016f351a20 - 0x000000016f351a00</span><br><span class="line">(long) $13 = 0x0000000000000020</span><br></pre></td></tr></table></figure>
<p>fp-sp=0x20, 因为上面开始开辟栈空间 0x30, 但存储 x29 和 x30 用了 0x10, 所以还剩 0x20 大小的空间可用。</p>
<h3 id="callee-test2"><a href="#callee-test2" class="headerlink" title="callee - test2"></a>callee - test2</h3><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/stack/20220930105031.png"></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Demo_fishhook`-[ViewController test2:b:]:</span><br><span class="line"> 0x1020672d8 <+0>: sub sp, sp, #0x20</span><br><span class="line"> 0x1020672dc <+4>: str x0, [sp, #0x18]</span><br><span class="line"> 0x1020672e0 <+8>: str x1, [sp, #0x10]</span><br><span class="line"> 0x1020672e4 <+12>: str w2, [sp, #0xc]</span><br><span class="line"> 0x1020672e8 <+16>: str w3, [sp, #0x8]</span><br><span class="line">-> 0x1020672ec <+20>: ldr w8, [sp, #0xc]</span><br><span class="line"> 0x1020672f0 <+24>: ldr w9, [sp, #0x8]</span><br><span class="line"> 0x1020672f4 <+28>: add w8, w8, w9</span><br><span class="line"> 0x1020672f8 <+32>: str w8, [sp, #0x4]</span><br><span class="line"> 0x1020672fc <+36>: ldr w0, [sp, #0x4]</span><br><span class="line"> 0x102067300 <+40>: add sp, sp, #0x20</span><br><span class="line"> 0x102067304 <+44>: ret </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">(lldb) register read</span><br><span class="line">General Purpose Registers:</span><br><span class="line"> x0 = 0x0000000102709160</span><br><span class="line"> x1 = 0x00000001020ab740 "test2:b:"</span><br><span class="line"> x2 = 0x0000000000000001</span><br><span class="line"> x3 = 0x0000000000000002</span><br><span class="line"> x4 = 0x0000000000000010</span><br><span class="line"> x5 = 0x0000000000000020</span><br><span class="line"> x6 = 0x000000016dd9d8f0</span><br><span class="line"> x7 = 0x0000000000000000</span><br><span class="line"> x8 = 0x00000001020be000 (void *)0x00000001020ab182: initWithCodeType:baseAddress:size:name:uuid:.hex + 7651</span><br><span class="line"> x9 = 0x0000000000000000</span><br><span class="line"> x10 = 0x000000000000002e</span><br><span class="line"> x11 = 0x0000000103021ee8</span><br><span class="line"> x12 = 0x000000000000002e</span><br><span class="line"> x13 = 0x0000000000000000</span><br><span class="line"> x14 = 0x0000000180964000</span><br><span class="line"> x15 = 0x000000020b12c000</span><br><span class="line"> x16 = 0x00000001020bf9b2 (void *)0xe12800000001020b</span><br><span class="line"> x17 = 0x00000001020672d8 Demo_fishhook`-[ViewController test2:b:] at ViewController.m:149</span><br><span class="line"> x18 = 0x0000000000000000</span><br><span class="line"> x19 = 0x0000000102709160</span><br><span class="line"> x20 = 0x0000000000000000</span><br><span class="line"> x21 = 0x00000001f59e3000 UIKitCore`_UIInternalPreference_IdleSchedulerTargetDeadlineFraction</span><br><span class="line"> x22 = 0x000000019b10ec13 </span><br><span class="line"> x23 = 0x000000019b7043f5 </span><br><span class="line"> x24 = 0x0000000000000000</span><br><span class="line"> x25 = 0x00000001f630c000 UIKitCore`_UIPreviewPresentationAnimator._startMediaTime</span><br><span class="line"> x26 = 0x00000001027083f0</span><br><span class="line"> x27 = 0x000000019bb48a24 </span><br><span class="line"> x28 = 0x00000001fab5f4e8 CoreFoundation`__NSArray0__struct</span><br><span class="line"> fp = 0x000000016dd9da20</span><br><span class="line"> lr = 0x00000001020672c4 Demo_fishhook`-[ViewController test1:] + 48 at ViewController.m:145:9</span><br><span class="line"> sp = 0x000000016dd9d9e0</span><br><span class="line"> pc = 0x00000001020672ec Demo_fishhook`-[ViewController test2:b:] + 20 at ViewController.m:151:15</span><br><span class="line"> cpsr = 0x40000000</span><br><span class="line"></span><br><span class="line">(lldb) </span><br></pre></td></tr></table></figure>
<br/>
<p>继续来看寄存器的值</p>
<ul>
<li>x0-x7, 存放参数和返回值<ul>
<li>x0 x1 跟 test1 一样,它们两个的 x0 值都是 0x0000000102709160</li>
<li>x2 = 0x0000000000000001, 入参 a 的值为 1</li>
<li>x3 = 0x0000000000000002, 入参 b 的值为 2</li>
<li>返回值,当断点在 <code>0x102067304</code> 的时候,变化的寄存器值如下,x0 就是返回值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">x0 = 0x0000000100f0ac70</span><br><span class="line">x8 = 0x0000000100a26000 (void *)0x0000000100a13182: initWithCodeType:baseAddress:size:name:uuid:.hex + 7651</span><br><span class="line">x9 = 0x0000000000000000</span><br><span class="line">sp = 0x000000016f4359e0</span><br><span class="line">pc = 0x00000001009cf2ec Demo_fishhook`-[ViewController test2:b:] + 20 at ViewController.m:151:15</span><br></pre></td></tr></table></figure>
sp 和 pc 变化是可以理解的,因为回退栈空间了;但 x8 和 x9 也发生变化就不知道原因了</li>
</ul>
</li>
<li>fp = 0x000000016dd9da20, 跟 test1 的 fp 值相同</li>
<li>lr = 0x00000001020672c4, 就是 test1 处的 0x1020672c4(即函数跳转处的下一条指令)</li>
</ul>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ol>
<li>lr 寄存器存储的是函数返回值,根据 lr 可以回溯到上个函数</li>
<li>test2 没有存储 x29 x30 到栈中, 而 test1 则有存储;因为 test2 是<code>叶子函数</code>,它里面没有调用其他函数。函数调用会用到 bl, 而 bl 则会把下一条指令的地址存入 x30 寄存器,会改变 x30 的值,所以出于保护现场的目的需要提前保存 x30 的值。而这里没有函数调用,意味着不会改变 x30, 所以就没有存储 x30 的意义了。</li>
<li>关于 fp 也是一个有趣的点,分别在 viewDidLoad 和 test1 处下断点,见下图<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/stack/20220930115939.png"></li>
</ol>
<ul>
<li>viewDidLoad.fp = 0x000000016f705a60</li>
<li>test1.fp = 0x000000016f705a20</li>
<li>由于 fp 是指针,那就看它所指向地址存储的数据(x 0x000000016f705a20):<code>0x16f705a20: 60 5a 70 6f 01 00 00 00</code>, 因为是小端,所有读取出来就是 <code>0x016f705a60</code>(viewDidLoad.fp),从而验证了该函数的 fp 指向上个函数的 fp</li>
<li><code>0x16f705a8: 8c f1 6f 00 01 00 00 00</code> 就是 <code>0x01006ff18c</code> (test1.lr),因为在 x29 x30 在 test1 里面就是连续存储的,见 <code>stp x29, x30, [sp, #0x20]</code></li>
</ul>
<h1 id="获取函数调用栈"><a href="#获取函数调用栈" class="headerlink" title="获取函数调用栈"></a>获取函数调用栈</h1><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>根据前面的 <code>Arm64/Demo/总结</code> 可知,如果要获取函数调用栈的话,首先要找到当前函数的 pc 和 lr 指针。</p>
<ol>
<li>获取当前函数的 pc, 得到当前函数的地址</li>
<li>根据 lr 得到上个函数的地址</li>
<li>循环进行 2 步,直到 lr 为空</li>
<li>拿到相关地址后符号化</li>
</ol>
<h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><p>从易后难,先看符号化的逻辑。</p>
<h3 id="符号化"><a href="#符号化" class="headerlink" title="符号化"></a>符号化</h3><p>看上面的 <code>Arm64/Demo</code>, 如果在 test2 获取的话,相关寄存器的值如下</p>
<ol>
<li>pc: 0x00000001020672ec</li>
<li>lr: 0x00000001020672c4</li>
</ol>
<p>而 test2 和 test1 的函数地址分别是</p>
<ol>
<li>0x1020672d8</li>
<li>0x102067294</li>
</ol>
<p>也就是说获取的地址值是大于实际地址值的,那这要怎么处理呢?带着这个问题,看下经典库 <a href="https://github.com/kstenerud/KSCrash/blob/498aa21d23541b0bb4990f8d3d20bea2c280a18b/Source/KSCrash/Recording/Tools/KSDynamicLinker.c#L232">KSCrash</a> 是怎么处理的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">bool ksdl_dladdr(const uintptr_t address, Dl_info* const info)</span><br><span class="line">{</span><br><span class="line"> info->dli_fname = NULL;</span><br><span class="line"> info->dli_fbase = NULL;</span><br><span class="line"> info->dli_sname = NULL;</span><br><span class="line"> info->dli_saddr = NULL;</span><br><span class="line"> // 在哪个 image</span><br><span class="line"> const uint32_t idx = imageIndexContainingAddress(address);</span><br><span class="line"> if(idx == UINT_MAX)</span><br><span class="line"> {</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> const struct mach_header* header = _dyld_get_image_header(idx);</span><br><span class="line"> // ALSR 值</span><br><span class="line"> const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);</span><br><span class="line"> </span><br><span class="line"> // 就是没有 ALSR 的 VMaddress</span><br><span class="line"> const uintptr_t addressWithSlide = address - imageVMAddrSlide;</span><br><span class="line"> // 虚拟基地址+ALSR值, 就是包含 ALSR 的虚拟内存地址, 其实就是 header, 验证结果见下面 segmentBase fishhook</span><br><span class="line"> const uintptr_t segmentBase = segmentBaseOfImageIndex(idx) + imageVMAddrSlide;</span><br><span class="line"> if(segmentBase == 0)</span><br><span class="line"> {</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> info->dli_fname = _dyld_get_image_name(idx);</span><br><span class="line"> info->dli_fbase = (void*)header;</span><br><span class="line"></span><br><span class="line"> // Find symbol tables and get whichever symbol is closest to the address.</span><br><span class="line"> const nlist_t* bestMatch = NULL;</span><br><span class="line"> uintptr_t bestDistance = ULONG_MAX;</span><br><span class="line"> uintptr_t cmdPtr = firstCmdAfterHeader(header);</span><br><span class="line"> if(cmdPtr == 0)</span><br><span class="line"> {</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++)</span><br><span class="line"> {</span><br><span class="line"> const struct load_command* loadCmd = (struct load_command*)cmdPtr;</span><br><span class="line"> // 符号表查询</span><br><span class="line"> if(loadCmd->cmd == LC_SYMTAB)</span><br><span class="line"> {</span><br><span class="line"> const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;</span><br><span class="line"> const nlist_t* symbolTable = (nlist_t*)(segmentBase + symtabCmd->symoff);</span><br><span class="line"> const uintptr_t stringTable = segmentBase + symtabCmd->stroff;</span><br><span class="line"></span><br><span class="line"> for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++)</span><br><span class="line"> {</span><br><span class="line"> // If n_value is 0, the symbol refers to an external object.</span><br><span class="line"> if(symbolTable[iSym].n_value != 0)</span><br><span class="line"> {</span><br><span class="line"> // 符号表的值,是不包含 ASLR 值的,所以上面 addressWithSlide 是要减去 ASLR</span><br><span class="line"> uintptr_t symbolBase = symbolTable[iSym].n_value;</span><br><span class="line"> // 两种相减,找距离最近的</span><br><span class="line"> uintptr_t currentDistance = addressWithSlide - symbolBase;</span><br><span class="line"> /*</span><br><span class="line"> `(addressWithSlide >= symbolBase)` : 因为 symbolBase 是具体符号值;而 addressWithSlide 则是需要查找的地址值, 二者可能想到,所以是 >= symbolBase</span><br><span class="line"> (currentDistance <= bestDistance) : 寻找最匹配的,距离越近越好</span><br><span class="line"> */</span><br><span class="line"> if((addressWithSlide >= symbolBase) &&</span><br><span class="line"> (currentDistance <= bestDistance))</span><br><span class="line"> {</span><br><span class="line"> // iSym 符号表的下标,跟 fishhook 里面的字符串计算是一样的道理</span><br><span class="line"> bestMatch = symbolTable + iSym;</span><br><span class="line"> bestDistance = currentDistance;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if(bestMatch != NULL)</span><br><span class="line"> {</span><br><span class="line"> // + imageVMAddrSlide(ASLR值),因为前面 addressWithSlide 有减去 imageVMAddrSlide ASLR 值</span><br><span class="line"> info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);</span><br><span class="line"> if(bestMatch->n_desc == 16)</span><br><span class="line"> {</span><br><span class="line"> // This image has been stripped. The name is meaningless, and</span><br><span class="line"> // almost certainly resolves to "_mh_execute_header"</span><br><span class="line"> info->dli_sname = NULL;</span><br><span class="line"> }</span><br><span class="line"> else</span><br><span class="line"> {</span><br><span class="line"> // 过掉符号修饰的前面的 `_`。</span><br><span class="line"> info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);</span><br><span class="line"> if(*info->dli_sname ==addressWithSlide - '_')</span><br><span class="line"> {</span><br><span class="line"> info->dli_sname++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 继续匹配下一个符号</span><br><span class="line"> cmdPtr += loadCmd->cmdsize;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> return true;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从上面代码可知,跟 fishhook 查找符号表原理是类似的,就是用 lr 的地址值到符号表里一一匹配,找到比 lr 地址值小并离 lr 地址值最近的那个符号就是该函数的符号。</p>
<h3 id="获取线程信息"><a href="#获取线程信息" class="headerlink" title="获取线程信息"></a>获取线程信息</h3><p>因为 KSCrash 这块的代码比较多,现在还没捋顺,于是就找到了《<a href="https://time.geekbang.org/column/intro/100024501?tab=catalog">iOS 开发高手课</a>》作者的 <a href="https://github.com/ming1016/DecoupleDemo/blob/master/DecoupleDemo/SMCallStack.m#L103">SMCallStack.m</a></p>
<br/>
<p>大致逻辑(只针对 Arm64 架构)如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">typedef struct SMStackFrame {</span><br><span class="line"> const struct SMStackFrame *const previous;</span><br><span class="line"> const uintptr_t return_address;</span><br><span class="line">} SMStackFrame;</span><br><span class="line"></span><br><span class="line">NSString *smStackOfThread(thread_t thread) {</span><br><span class="line"> uintptr_t buffer[100];</span><br><span class="line"> int i = 0;</span><br><span class="line"> _STRUCT_MCONTEXT64 machineContext;</span><br><span class="line"> mach_msg_type_number_t state_count = ARM_THREAD_STATE64_COUNT;</span><br><span class="line"> kern_return_t kr = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&machineContext.__ss, &state_count);</span><br><span class="line"> if (kr != KERN_SUCCESS) {</span><br><span class="line"> return [NSString stringWithFormat:@"Fail get thread: %u", thread];</span><br><span class="line"> }</span><br><span class="line"> const uintptr_t instructionAddress = machineContext.__ss.__pc;</span><br><span class="line"> buffer[i++] = instructionAddress;</span><br><span class="line"> uintptr_t linkRegisterPointer = machineContext.__ss.__lr;</span><br><span class="line"> if (linkRegisterPointer) {</span><br><span class="line"> buffer[i++] = linkRegisterPointer;</span><br><span class="line"> }</span><br><span class="line"> SMStackFrame stackFrame = {0};</span><br><span class="line"> const uintptr_t framePointer = machineContext.__ss.__fp;</span><br><span class="line"> </span><br><span class="line"> vm_size_t bytesCopied = 0;</span><br><span class="line"> if (framePointer == 0 || vm_read_overwrite(mach_task_self(), (vm_address_t)(void *)framePointer, (vm_size_t)sizeof(stackFrame), (vm_address_t)&stackFrame, &bytesCopied) != KERN_SUCCESS) {</span><br><span class="line"> return @"Fail frame pointer";</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> bytesCopied = 0;</span><br><span class="line"> </span><br><span class="line"> for (; ; i++) {</span><br><span class="line"> buffer[i] = stackFrame.return_address;</span><br><span class="line"> if (buffer[i] == 0 || stackFrame.previous == 0 || vm_read_overwrite(mach_task_self(), (vm_address_t)(void *)stackFrame.previous, (vm_size_t)sizeof(stackFrame), (vm_address_t)&stackFrame, &bytesCopied) != KERN_SUCCESS) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // xxxxx</span><br><span class="line"> return @"";</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>代码大致逻辑能看懂:根据系统函数获取 pc fp lr, 再循环获取 lr 加入数组。<br>但不知道为什么要这样写,只能找到根据大佬的代码去搜索相关资料,基本都是操作系统领域的相关知识。</p>
<h4 id="STRUCT-MCONTEXT64"><a href="#STRUCT-MCONTEXT64" class="headerlink" title="_STRUCT_MCONTEXT64"></a>_STRUCT_MCONTEXT64</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">#define _STRUCT_MCONTEXT64 struct __darwin_mcontext64</span><br><span class="line">_STRUCT_MCONTEXT64</span><br><span class="line">{</span><br><span class="line"> _STRUCT_ARM_EXCEPTION_STATE64 __es;</span><br><span class="line"> _STRUCT_ARM_THREAD_STATE64 __ss;</span><br><span class="line"> _STRUCT_ARM_NEON_STATE64 __ns;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">_STRUCT_ARM_THREAD_STATE64</span><br><span class="line">{</span><br><span class="line"> __uint64_t __x[29]; /* General purpose registers x0-x28 */</span><br><span class="line"> __uint64_t __fp; /* Frame pointer x29 */</span><br><span class="line"> __uint64_t __lr; /* Link register x30 */</span><br><span class="line"> __uint64_t __sp; /* Stack pointer x31 */</span><br><span class="line"> __uint64_t __pc; /* Program counter */</span><br><span class="line"> __uint32_t __cpsr; /* Current program status register */</span><br><span class="line"> __uint32_t __pad; /* Same size for 32-bit or 64-bit clients */</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>获取线程寄存器相关信息。</p>
<h4 id="thread-get-state"><a href="#thread-get-state" class="headerlink" title="thread_get_state"></a>thread_get_state</h4><p>在 <a href="https://github.com/apple-oss-distributions/xnu/blob/main/libsyscall/wrappers/thread_register_state.c#L63">xnu</a> 源码中找到了搜索到了蛛丝马迹,获取到 state 内容后使用相关寄存器。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">#if defined(__i386__)</span><br><span class="line"> i386_thread_state_t state = {};</span><br><span class="line"> thread_state_flavor_t flavor = x86_THREAD_STATE32;</span><br><span class="line"> mach_msg_type_number_t count = i386_THREAD_STATE_COUNT;</span><br><span class="line">#elif defined(__x86_64__)</span><br><span class="line"> x86_thread_state64_t state = {};</span><br><span class="line"> thread_state_flavor_t flavor = x86_THREAD_STATE64;</span><br><span class="line"> mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;</span><br><span class="line">#elif defined(__arm__)</span><br><span class="line"> arm_thread_state_t state = {};</span><br><span class="line"> thread_state_flavor_t flavor = ARM_THREAD_STATE;</span><br><span class="line"> mach_msg_type_number_t count = ARM_THREAD_STATE_COUNT;</span><br><span class="line">#elif defined(__arm64__)</span><br><span class="line"> arm_thread_state64_t state = {};</span><br><span class="line"> thread_state_flavor_t flavor = ARM_THREAD_STATE64;</span><br><span class="line"> mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT;</span><br><span class="line">#else</span><br><span class="line">#error thread_get_register_pointer_values not defined for this architecture</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line"> kern_return_t ret = thread_get_state(thread, flavor, (thread_state_t)&state, &count);</span><br><span class="line"> </span><br><span class="line">// xxxx</span><br><span class="line">if (sp) {</span><br><span class="line"> uintptr_t __sp = arm_thread_state64_get_sp(state);</span><br><span class="line"> if (__sp > 128) {</span><br><span class="line"> *sp = __sp - 128 /* redzone */;</span><br><span class="line"> } else {</span><br><span class="line"> *sp = 0;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> push_register_value(arm_thread_state64_get_lr(state));</span><br><span class="line"></span><br><span class="line"> for (int i = 0; i < 29; i++) {</span><br><span class="line"> push_register_value(state.__x[i]);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="vm-read-overwrite"><a href="#vm-read-overwrite" class="headerlink" title="vm_read_overwrite"></a>vm_read_overwrite</h4><p><a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_read.html">vm_read_overwrite</a></p>
<blockquote>
<p>The vm_read and vm_read_overwrite functions read a portion of a task’s virtual memory (they enable tasks to read other tasks’ memory). The vm_read function returns the data in a dynamically allocated array of bytes; the vm_read_overwrite function places the data into a caller-specified buffer (the data_in parameter).</p>
</blockquote>
<p>上面的代码就是调用系统函数 <code>vm_read_overwrite</code> 从 fp 的位置开始读取内存,给 <code>SMStackFrame</code> 结构体赋值,从而得到 lr, 再根据 lr 递归调用获取整个函数的调用链。 </p>
<h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1><h2 id="ksdl-dladdr"><a href="#ksdl-dladdr" class="headerlink" title="ksdl_dladdr"></a>ksdl_dladdr</h2><p>从上面的调试可知,<code>dladdr</code> 就是 <code>lldb</code> 命令 <code>image lookup -a 0x00xxx</code> 的代码实现,二者功能是类似的。<br><br/></p>
<blockquote>
<p>NAME<br> dladdr – find the image containing a given address</p>
</blockquote>
<p>上面是在终端使用 <code>man dladdr</code> 得到的内容。</p>
<p>有一个问题:为什么不使用系统的 <code>dladdr</code> 函数?而要自己实现 <code>ksdl_dladdr</code> 呢?<br>找到了相关问题 <a href="https://github.com/bestswifter/BSBacktraceLogger/issues/8">BSBacktraceLogger - 请问为什么不用系统提供的dladdr方法,而需要自己写一个fl_dladdr呢? #8</a>,但作者没有给出有用的答案。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/** async-safe version of dladdr.</span><br><span class="line"> *</span><br><span class="line"> * This method searches the dynamic loader for information about any image</span><br><span class="line"> * containing the specified address. It may not be entirely successful in</span><br><span class="line"> * finding information, in which case any fields it could not find will be set</span><br><span class="line"> * to NULL.</span><br><span class="line"> *</span><br><span class="line"> * Unlike dladdr(), this method does not make use of locks, and does not call</span><br><span class="line"> * async-unsafe functions.</span><br><span class="line"> *</span><br><span class="line"> * @param address The address to search for.</span><br><span class="line"> * @param info Gets filled out by this function.</span><br><span class="line"> * @return true if at least some information was found.</span><br><span class="line"> */</span><br><span class="line">bool ksdl_dladdr(const uintptr_t address, Dl_info* const info);</span><br></pre></td></tr></table></figure>
<p>看 <code>ksdl_dladdr</code> 的声明可知,从侧面反应系统的 <code>dladdr</code> 是同步的,会使用锁,可能会导致耗时。</p>
<p>有兴趣的可以查看 <a href="https://github.com/apple-oss-distributions/dyld/blob/f73171cf0a177f453fdb124952908fe83864acab/dyld/DyldAPIs.cpp#L1016">dyld</a> 里面关于 <code>dladdr</code> 的实现。</p>
<h2 id="lldb-backtrace-thread"><a href="#lldb-backtrace-thread" class="headerlink" title="lldb backtrace thread"></a>lldb backtrace thread</h2><p>不管是 <a href="https://github.com/ming1016/DecoupleDemo/blob/master/DecoupleDemo/SMCallStack.m#L103">SMCallStack</a> 还是 <a href="https://github.com/bestswifter/BSBacktraceLogger/issues/8">BSBacktraceLogger</a> 输出结果跟 <code>lldb bt</code> 的结果还是有些差异的,可能跟着两个库代码很久没更新有关系,<a href="https://github.com/microsoft/plcrashreporter">plcrashreporter</a> 的输出结果最接近(因为不知道怎么根据 KSCrash 直接获取调用堆栈),代码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];</span><br><span class="line"> PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];</span><br><span class="line"> NSData *data = [crashReporter generateLiveReport];</span><br><span class="line"> PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];</span><br><span class="line"> NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter</span><br><span class="line">withTextFormat:PLCrashReportTextFormatiOS];</span><br></pre></td></tr></table></figure>
<br>
<p>所以,就想找到 <code>lldb backtrace thread</code> 的源码实现。<br>下载 <a href="https://github.com/apple-oss-distributions/lldb">lldb</a> 工程,然后搜索 <code>Backtrace thread</code> 找到如下相关代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">lldb::ThreadSP</span><br><span class="line"> GetExtendedBacktraceThread (ConstString type);</span><br><span class="line"></span><br><span class="line">// ----</span><br><span class="line">uint32_t</span><br><span class="line">SBThread::GetExtendedBacktraceOriginatingIndexID ()</span><br><span class="line">{</span><br><span class="line"> ThreadSP thread_sp(m_opaque_sp->GetThreadSP());</span><br><span class="line"> if (thread_sp)</span><br><span class="line"> return thread_sp->GetExtendedBacktraceOriginatingIndexID();</span><br><span class="line"> return LLDB_INVALID_INDEX32;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// ----</span><br><span class="line">typedef std::shared_ptr<lldb_private::Thread> ThreadSP;</span><br></pre></td></tr></table></figure>
<p>坑爹,看到 <code>lldb_private</code> 就知道凉凉了,因为它是私有的,只在 llvm 官网找到了相关定义 <a href="https://lldb.llvm.org/cpp_reference/classlldb__private_1_1Thread.html">lldb_private::Thread Class Reference</a>。</p>
<p>后面又想过是否可以通过查看 GDB 的源码来查看 <code>backtrace thread</code> 的源码实现,嗯,是个好想法!!!</p>
<h2 id="objc-msgSend"><a href="#objc-msgSend" class="headerlink" title="objc_msgSend"></a>objc_msgSend</h2><p>我们知道,OC 的方法调用最终都会走 <code>objc_msgSend</code> 这个汇编实现的函数,那为什么它没有出现在调用堆栈里面呢?<br>可以猜测下答案:因为 <code>objc_msgSend</code> 不会使用栈空间。<br>下面来 debug 调试下,还是前面 <code>viewDidLoad</code> 调用 <code>test1</code> 的例子,分别对比在这两个函数汇编下寄存器的值。</p>
<hr>
<p>call test1</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">Demo_fishhook`-[ViewController viewDidLoad]:</span><br><span class="line"> 0x104e32f58 <+0>: sub sp, sp, #0x40</span><br><span class="line"> 0x104e32f5c <+4>: stp x29, x30, [sp, #0x30]</span><br><span class="line"> 0x104e32f60 <+8>: add x29, sp, #0x30</span><br><span class="line"> 0x104e32f64 <+12>: stur x0, [x29, #-0x8]</span><br><span class="line"> 0x104e32f68 <+16>: stur x1, [x29, #-0x10]</span><br><span class="line"> 0x104e32f6c <+20>: ldur x8, [x29, #-0x8]</span><br><span class="line"> 0x104e32f70 <+24>: add x0, sp, #0x10</span><br><span class="line"> 0x104e32f74 <+28>: str x8, [sp, #0x10]</span><br><span class="line"> 0x104e32f78 <+32>: adrp x8, 89</span><br><span class="line"> 0x104e32f7c <+36>: ldr x8, [x8, #0x6c0]</span><br><span class="line"> 0x104e32f80 <+40>: str x8, [sp, #0x18]</span><br><span class="line"> 0x104e32f84 <+44>: adrp x8, 88</span><br><span class="line"> 0x104e32f88 <+48>: ldr x1, [x8, #0xbe8]</span><br><span class="line"> 0x104e32f8c <+52>: bl 0x104e730a4 ; symbol stub for: objc_msgSendSuper2</span><br><span class="line"> 0x104e32f90 <+56>: ldur x0, [x29, #-0x8]</span><br><span class="line"> 0x104e32f94 <+60>: adrp x8, 88</span><br><span class="line"> 0x104e32f98 <+64>: ldr x1, [x8, #0xbf0]</span><br><span class="line"> 0x104e32f9c <+68>: mov w2, #0x1</span><br><span class="line">-> 0x104e32fa0 <+72>: bl 0x104e73098 ; symbol stub for: objc_msgSend</span><br><span class="line"> 0x104e32fa4 <+76>: str w0, [sp, #0xc]</span><br><span class="line"> 0x104e32fa8 <+80>: ldr w9, [sp, #0xc]</span><br><span class="line"> 0x104e32fac <+84>: mov x8, x9</span><br><span class="line"> 0x104e32fb0 <+88>: adrp x0, 81</span><br><span class="line"> 0x104e32fb4 <+92>: add x0, x0, #0x440 ; @"res: %d"</span><br><span class="line"> 0x104e32fb8 <+96>: mov x9, sp</span><br><span class="line"> 0x104e32fbc <+100>: str x8, [x9]</span><br><span class="line"> 0x104e32fc0 <+104>: bl 0x104e72720 ; symbol stub for: NSLog</span><br><span class="line"> 0x104e32fc4 <+108>: ldp x29, x30, [sp, #0x30]</span><br><span class="line"> 0x104e32fc8 <+112>: add sp, sp, #0x40</span><br><span class="line"> 0x104e32fcc <+116>: ret </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) register read</span><br><span class="line">General Purpose Registers:</span><br></pre></td></tr></table></figure>
<hr>
<p>objc_msgSend</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Demo_fishhook`objc_msgSend:</span><br><span class="line">-> 0x104e73098 <+0>: nop </span><br><span class="line"> 0x104e7309c <+4>: ldr x16, #0xd6e4 ; (void *)0x0000000198569ce0: objc_msgSend</span><br><span class="line"> 0x104e730a0 <+8>: br x16</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(lldb) si</span><br><span class="line">(lldb) register read</span><br><span class="line">General Purpose Registers:</span><br></pre></td></tr></table></figure>
<p>对比发现两者变化的仅仅是 <code>lr</code> 和 <code>pc</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// test1</span><br><span class="line">lr = 0x0000000104e32f90 Demo_fishhook`-[ViewController viewDidLoad] + 56 at ViewController.m:84:16</span><br><span class="line">pc = 0x0000000104e32fa0 Demo_fishhook`-[ViewController viewDidLoad] + 72 at ViewController.m:84:15</span><br><span class="line"></span><br><span class="line">// objc_msgSend</span><br><span class="line">lr = 0x0000000104e32fa4 Demo_fishhook`-[ViewController viewDidLoad] + 76 at ViewController.m:84:9</span><br><span class="line">pc = 0x0000000104e73098 Demo_fishhook`symbol stub for: objc_msgSend</span><br></pre></td></tr></table></figure>
<p>经过上面的输出可知,<code>objc_msgSend</code> 共用当前函数的栈空间,没有产生新的栈空间,所以就不会出现在函数调用栈里面了,所以,这是 <code>objc_msgSend</code> 用汇编实现的原因之一吗?</p>
<h1 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h1><ul>
<li><a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a></li>
<li><a href="https://github.com/ARM-software/abi-aa/blob/60a8eb8c55e999d74dac5e368fc9d7e36e38dda4/aapcs64/aapcs64.rst#the-stack">Procedure Call Standard for the Arm® 64-bit Architecture (AArch64)</a></li>
<li><a href="https://en.wikipedia.org/wiki/Calling_convention#ARM_(A64)">Calling_convention#ARM_(A64)</a></li>
<li><a href="https://zh.wikipedia.org/wiki/%E5%AF%84%E5%AD%98%E5%99%A8">寄存器</a></li>
<li><a href="https://time.geekbang.org/column/intro/100024501?tab=catalog">iOS 开发高手课</a></li>
<li><a href="https://blackteachinese.github.io/2017/07/12/arm64/">10分钟入门arm64汇编</a></li>
<li><a href="https://github.com/bestswifter/BSBacktraceLogger/issues/8">BSBacktraceLogger - 请问为什么不用系统提供的dladdr方法,而需要自己写一个fl_dladdr呢? #8</a></li>
<li><a href="https://github.com/apple-oss-distributions/dyld/blob/f73171cf0a177f453fdb124952908fe83864acab/dyld/DyldAPIs.cpp#L1016">dyld</a></li>
<li><a href="https://github.com/ming1016/DecoupleDemo/blob/master/DecoupleDemo/SMCallStack.m#L103">SMCallStack</a></li>
<li><a href="https://github.com/kstenerud/KSCrash/blob/498aa21d23541b0bb4990f8d3d20bea2c280a18b/Source/KSCrash/Recording/Tools/KSDynamicLinker.c#L232">KSCrash</a></li>
<li><a href="https://github.com/microsoft/plcrashreporter">plcrashreporter</a></li>
<li><a href="https://github.com/apple-oss-distributions/lldb">lldb</a></li>
<li><a href="http://djs66256.github.io/2018/01/21/2018-01-21-%E8%BF%90%E8%A1%8C%E6%97%B6%E8%8E%B7%E5%8F%96%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%A0%88/">运行时获取函数调用栈</a></li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2022/09/20/fishhook/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="joakim.liu">
<meta itemprop="description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="牛易疯先森的开发记录">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2022/09/20/fishhook/" class="post-title-link" itemprop="url">从 Fishhook 学到了什么?</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2022-09-20 10:10:39" itemprop="dateCreated datePublished" datetime="2022-09-20T10:10:39+08:00">2022-09-20</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>最近把《<a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a>》这本书又重新温习了下,加深了对可执行文件的理解,这篇文章就结合 <a href="https://github.com/facebook/fishhook">fishhook</a> 来实践一下。</p>
<h1 id="Demo-NSLog"><a href="#Demo-NSLog" class="headerlink" title="Demo - NSLog"></a>Demo - NSLog</h1><p>先从一个 NSLog 的 Demo 实践开始,讲解调用系统库函数 <code>NSLog</code> 的执行流程。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> NSString * appDelegateClassName;</span><br><span class="line"> @autoreleasepool {</span><br><span class="line"> // Setup code that might create autoreleased objects goes here.</span><br><span class="line"> appDelegateClassName = NSStringFromClass([AppDelegate class]);</span><br><span class="line"> NSLog(@"%@", @"hello world");</span><br><span class="line"> }</span><br><span class="line"> return UIApplicationMain(argc, argv, nil, appDelegateClassName);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="lldb"><a href="#lldb" class="headerlink" title="lldb"></a>lldb</h2><p>在 NSLog 处下断点,并在 Xcode 设置 <code>debug -> debug workflow -> always show disassembly</code> 查看其汇编实现。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> 0x10fcf0a79 <+89>: leaq 0x26f20(%rip), %rdi ; @"%@"</span><br><span class="line"> 0x10fcf0a80 <+96>: leaq 0x26f39(%rip), %rsi ; @"hello world"</span><br><span class="line">-> 0x10fcf0a87 <+103>: movb $0x0, %al</span><br><span class="line"> 0x10fcf0a89 <+105>: callq 0x10fd12000 ; symbol stub for: NSLog</span><br><span class="line"> 0x10fcf0a8e <+110>: movq -0x20(%rbp), %rdi</span><br><span class="line"> 0x10fcf0a92 <+114>: callq 0x10fd122ac ; symbol stub for: objc_autoreleasePoolPop</span><br></pre></td></tr></table></figure>
<p>在 <code>0x10fd12000</code> 处下断点</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) b 0x10fd12000</span><br><span class="line">Breakpoint 3: where = Demo_fishhook`symbol stub for: NSLog, address = 0x000000010fd12000</span><br></pre></td></tr></table></figure>
<p>单步执行过掉 <code>NSLog</code> 处的断点,到</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Demo_fishhook`NSLog:</span><br><span class="line">-> 0x10fd12000 <+0>: jmpq *0x50ea(%rip) ; (void *)0x00007fff207ee762: NSLog</span><br></pre></td></tr></table></figure>
<p>这条指令的意思获取 <code>rip + 0x50ea</code> 地址(A),然后跳转到该地址存储的值(B)(类比二级指针)。</p>
<p>那我们先找出 rip 的地址</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">(lldb) register read</span><br><span class="line">General Purpose Registers:</span><br><span class="line"> rax = 0x0000600002f3e400</span><br><span class="line"> rbx = 0x000000010fda3060</span><br><span class="line"> rcx = 0x0000000114fbf600 dyld`_main_thread</span><br><span class="line"> rdx = 0x000000000000002c</span><br><span class="line"> rdi = 0x000000010fd179a0 @"%@"</span><br><span class="line"> rsi = 0x000000010fd179c0 @"hello world"</span><br><span class="line"> rbp = 0x00007ff7b0212ca0</span><br><span class="line"> rsp = 0x00007ff7b0212c78</span><br><span class="line"> r8 = 0x00007fff862a40c0 libsystem_pthread.dylib`_pthread_keys</span><br><span class="line"> r9 = 0x0000000000000000</span><br><span class="line"> r10 = 0x00007fff862da642 (void *)0xe6b800007fff862d</span><br><span class="line"> r11 = 0x00007fff2019c15c libobjc.A.dylib`-[NSObject autorelease]</span><br><span class="line"> r12 = 0x0000000114fbf3a0 dyld`_NSConcreteStackBlock</span><br><span class="line"> r13 = 0x00007ff7b0212d68</span><br><span class="line"> r14 = 0x000000010ff98e14 dyld_sim`start_sim</span><br><span class="line"> r15 = 0x0000000114fb3010 dyld`dyld4::sConfigBuffer</span><br><span class="line"> rip = 0x000000010fd12000 Demo_fishhook`symbol stub for: NSLog</span><br><span class="line"> rflags = 0x0000000000000246</span><br><span class="line"> cs = 0x000000000000002b</span><br><span class="line"> fs = 0x0000000000000000</span><br><span class="line"> gs = 0x0000000000000000</span><br></pre></td></tr></table></figure>
<p>当前指令还卡在 <code>0x000000010fd12000</code> 处,所以 rip 还是它。那怎么知道 <code>0x000000010fd12000</code> 下一条指令的地址呢?也就是 <code>0x000000010fd12000</code> 这条指令的长度(PS: 按道理应该有文档能找到 AT&T X86-64 汇编 jmpq 指令的长度,但没搜到)。<br>后面用 <code>lldb dis</code> 乱猜一通,于是就有了下面的结果。<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923110933.png"></p>
<p><code>0x10fd12000</code> 下一条指令是 <code>0x10fd12006</code>,所以地址(A)就是 <code>0x000000010fd170f0</code>,而 <code>0x000000010fd170f0</code> 里面存储的值(B)就是 <code>0x00007fff207ee762</code>,在系统库 <code>Foundation</code> 里面,也就是找到 <code>NSLog</code> 的实现了。这跟 <a href="https://zhang759740844.github.io/2019/07/07/fishhook/">fishhook 源码解析</a> 里面提到的第一次会通过 <code>__stub_helper</code> 去查找不符合,这里 <code>__DATA,__la_symbol_ptr</code> 直接存储的就是实际地址值了。(PS: 我 Xcode 版本是 <code>Version 13.1 (13A1030d)</code>,通过下断点得知是 dyld4, 我以为是 dyld4 做了什么优化,把电脑重启后再次运行还是一样的结果。)</p>
<p><strong>Note</strong>: </p>
<ol>
<li>大小端,内存地址存储的值是小端模式,即 <code>0x10fd170f0: 62 e7 7e 20 ff 7f 00 00</code> </li>
<li>lldb image 指令的其他玩法,可以在 help 调试下输入 <code>image help</code> 查看</li>
</ol>
<h2 id="MachOView"><a href="#MachOView" class="headerlink" title="MachOView"></a>MachOView</h2><p>既然 lldb 调试推理不出 <code>__stub_helper</code>,那就看下是否能通过 macho 可执行文件里面存储的原始值能推理出来不?</p>
<p>还是前面的断点</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image lookup -a 0x10fd12000</span><br><span class="line"> Address: Demo_fishhook[0x0000000100027000] (Demo_fishhook.__TEXT.__stubs + 18)</span><br><span class="line"> Summary: Demo_fishhook`symbol stub for: NSLog</span><br></pre></td></tr></table></figure>
<p>在 <code>__TEXT.__stubs</code> section, 用 <a href="https://sourceforge.net/projects/machoview/">MachOView</a> 来查看该 Demo 的可执行文件。</p>
<p><strong>Note</strong>:</p>
<ol>
<li><code>0x10fd12000</code> 是虚拟内存地址</li>
<li><code>0x0000000100027000</code> 是加上虚拟基地址的文件偏移(offset), 而虚拟基地址一般都是 <code>0x0000000100000000</code>, 即 2^32.</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image list // 得出该可执行文件 image 在虚拟内存中的起始地址:`0x000000010fceb000`</span><br><span class="line">[ 0] 0D70A5F7-C54D-312D-B242-ADE1AB9BEF9D 0x000000010fceb000 /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app/Demo_fishhook </span><br><span class="line"> /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app.dSYM/Contents/Resources/DWARF/Demo_fishhook</span><br><span class="line"></span><br><span class="line">(lldb) image list -o -f // 得到可执行文件的 ASLR 值</span><br><span class="line">[ 0] 0x000000000fceb000 /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app/Demo_fishhook </span><br><span class="line"></span><br><span class="line">(lldb) p/x 0x000000010fceb000 - 0x000000000fceb000</span><br><span class="line">(long) $0 = 0x0000000100000000 // 上面两个地址相减,就得到了虚拟基址</span><br><span class="line"></span><br><span class="line">(lldb) p/x 0x0000000100027000 - 0x0000000100000000 // 得到 `Demo_fishhook.__TEXT.__stubs` 在 macho 文件中的 offset</span><br><span class="line">(long) $1 = 0x0000000000027000</span><br><span class="line"></span><br><span class="line">(lldb) p/x 0x10fd12006 + 0x50ea - 0x000000000fceb000 // 得到前面提到的地址 A, 还是通过前面 lldb 计算得来,发现跟下面 MachOView 所查看到的是一样的</span><br><span class="line">(int) $2 = 0x000000010002c0f0</span><br></pre></td></tr></table></figure>
<p>在 <code>RVA</code> tab 找到 <code>0x000000010002c0f0</code> 地址<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923124046.png"><br>注意 <code>Data</code> 那一栏的值 <code>00000001000273D8</code>, 继续查找。</p>
<ul>
<li>RVA: Relative Virtual Address, 相对虚拟地址(没添加 ASLR 的值)</li>
<li>RAW: offset, 在 macho 文件中的偏移</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923124324.png"><br><strong>push 0x59</strong><br><code>push 0x59</code>: 表示将 0x59 立即数入栈,而我们知道栈一般用做函数调用时的传参。<br>那 0x59 立即数又是什么意思呢?<br>根据 <a href="https://tannerjin.github.io/2019/09/25/AntiHook/">AntiHook</a> 的内容可知</p>
<blockquote>
<p>其实在dyld源码里,dyld_ stub_bind最后会调用fastBindLazySymbol函数,这个函数的第二个参数是lazyBindingInfoOffset, 即0x0120是Binding Info或者Lazy Binding Info区起始开始到符号信息的偏移,而符号信息如下图</p>
</blockquote>
<p>因为 <code>NSLog</code> 符号在 <code>__DATA,__la_symbol_ptr</code>(la: lazy), 所以就是相对于 <code>Lazy Binding Info</code> 偏移 0x59 。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x1000370F8 + 0x59</span><br><span class="line">(long) $17 = 0x0000000100037151</span><br></pre></td></tr></table></figure>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923124908.png"><br>嗯,有趣的事情来了,这里竟然有符号名 <code>name(_NSLog)</code>。<br>那 <code>dylib(3)</code> 呢?表示该符号在所加载的第3个 dylib 里面(如下图)。<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923125118.png"><br><strong>Note:</strong> 从这里可以看出,进行链接的时候会把动态库里符号的相关信息存储起来(即:该符号属于哪个动态库)。</p>
<p><strong>jmp 0x10002733c</strong><br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923125508.png"></p>
<ul>
<li><code>lea r11, qword ptr [rip + 0x4cbd]</code><ul>
<li>将 <code>rip + 0x4cbd = 0x100027343 + 0x4cbd = 0x000000010002c000</code> 地址的值存入 r11 寄存器</li>
</ul>
</li>
<li><code>push r11</code><ul>
<li>所以此时栈中有两个参数:前面的 0x59; 这里的 <code>0x000000010002c000</code></li>
</ul>
</li>
<li><code>jmp qword ptr [rip + 0x4d85]</code><ul>
<li>跳转到 <code>rip + 0x4cbd = 0x10002734B + 0x4d85 = 0x000000010002c0d0</code> 执行函数</li>
</ul>
</li>
</ul>
<p>注意:这里分为两块</p>
<ol>
<li>前面这一块的就是 <code>__stub_helper</code> 的具体执行逻辑</li>
<li>后面这一块就是传参、然后调用 <code>__stub_helper</code> // 而这里的参数具体在前面讲解 0x59 的时候有提到</li>
</ol>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923130136.png"></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x000000000fceb000 + 0x10002C000</span><br><span class="line">(long) $24 = 0x000000010fd17000</span><br><span class="line">(lldb) x 0x000000010fd17000</span><br><span class="line">0x10fd17000: 00 00 00 00 00 00 00 00 00 94 4d 8a ff 7f 00 00 ..........M.....</span><br><span class="line">0x10fd17010: 08 28 2a 86 ff 7f 00 00 1a 33 25 20 ff 7f 00 00 .(*......3% ....</span><br></pre></td></tr></table></figure>
<p>诶,在程序启动后,它的值还是为 0, 这是为什么呢?</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923130225.png"><br>奈斯,终于看到 <code>dyld_stub_binder</code> 了,并且它已经有值了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x000000000fceb000 + 0x10002C0D0</span><br><span class="line">(long) $25 = 0x000000010fd170d0</span><br><span class="line">(lldb) x 0x000000010fd170d0</span><br><span class="line">0x10fd170d0: 80 a6 2c 86 ff 7f 00 00 b9 85 36 20 ff 7f 00 00 ..,.......6 ....</span><br><span class="line">0x10fd170e0: 10 ad 7d 20 ff 7f 00 00 84 c0 3f 20 ff 7f 00 00 ..} ......? ....</span><br></pre></td></tr></table></figure>
<p><strong>Note</strong>:这里的 <code>__DATA,__nl_symbol_ptr</code> 和 <code>__DATA,__got</code> Section 都是属于非懒加载的,在程序启动时 dyld 就会修正这些符号值。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131201.png" alt="__DATA,__nl_symbol_ptr Section"></p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131234.png" alt="__DATA,__got Section"></p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131308.png" alt="__DATA,__la_symbol_ptr Section"><br><strong>Note</strong>: 注意这里的 Indirect Sym Index(Reserved1): 168, 后面讲解 fishhook 的时候会提到。</p>
<p>因为它们存在于有可读可写权限的 <code>DATA Segment</code>。<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131011.png" alt="TEXT Segment"><br>可读可执行。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131111.png" alt="DATA Segment"><br>可读可写。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>NSLog</code> 这块的逻辑大致分为</p>
<ol>
<li>lazy binding 的符号地址最开始指向 <code>__TEXT,__stubs</code> 里所指向的方法,调用该方法的时候,它会调用 <code>__DATA,__la_symbol_ptr</code> 所指向地址里面的方法</li>
<li>未绑定的时候,<code>__DATA,__la_symbol_ptr</code> 指向 <code>___TEXT,__stub_helper</code> ,而后者会执行系统函数(dyld_stub_binder)找到方法实现后,会修改 <code>__DATA,__la_symbol_ptr</code> 的值,从而指向实际的函数地址</li>
<li>已绑定的时候,<code>__DATA,__la_symbol_ptr</code> 则指向实际的函数地址。// 而 fishhook 就是这么干的,修改 <code>__DATA,__la_symbol_ptr</code> 里面相关符号的值</li>
</ol>
<h1 id="fishhook"><a href="#fishhook" class="headerlink" title="fishhook"></a>fishhook</h1><p>有了前面 <code>NSLog</code> lazy binding 的调试经验,现在调试 fishhook 就会轻松很多。这里的 Demo 参考自 <a href="https://zhang759740844.github.io/2019/07/07/fishhook/">fishhook 源码解析</a>。</p>
<h2 id="rebind-symbols"><a href="#rebind-symbols" class="headerlink" title="rebind_symbols"></a>rebind_symbols</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">struct rebindings_entry {</span><br><span class="line"> struct rebinding *rebindings; // rebinding 类型的数组</span><br><span class="line"> size_t rebindings_nel; // 该数组的长度</span><br><span class="line"> struct rebindings_entry *next; // 链表的下一个 entry</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">// 链表头</span><br><span class="line">static struct rebindings_entry *_rebindings_head;</span><br><span class="line"></span><br><span class="line">int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {</span><br><span class="line"> // 维护一个 rebindings_entry 的结构</span><br><span class="line"> // 将 rebinding 的多个实例组织成一个链表</span><br><span class="line"> int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);</span><br><span class="line"> // 判断是否 malloc 失败,失败会返回 -1</span><br><span class="line"> if (retval < 0) {</span><br><span class="line"> return retval;</span><br><span class="line"> }</span><br><span class="line"> // _rebindings_head -> next 是第一次调用的标志符,NULL 则代表第一次调用</span><br><span class="line"> if (!_rebindings_head->next) {</span><br><span class="line"> // 第一次调用,将 _rebind_symbols_for_image 注册为回调</span><br><span class="line"> _dyld_register_func_for_add_image(_rebind_symbols_for_image);</span><br><span class="line"> } else {</span><br><span class="line"> // 先获取 dyld 镜像数量</span><br><span class="line"> uint32_t c = _dyld_image_count();</span><br><span class="line"> for (uint32_t i = 0; i < c; i++) {</span><br><span class="line"> // 根据下标依次进行重绑定过程</span><br><span class="line"> _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 返回状态值</span><br><span class="line"> return retval;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="prepend-rebindings"><a href="#prepend-rebindings" class="headerlink" title="prepend_rebindings"></a>prepend_rebindings</h2><p>该方法使用链表存储 <code>rebindings_entry</code> 结构,使用头插法将一个链表串起来,链表头用 <code>_rebindings_head</code> 保存。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">static struct rebindings_entry *_rebindings_head;</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">rebindings_head: 静态的链表头</span><br><span class="line">rebindings: 方法符号数组</span><br><span class="line">nel: 数组长度</span><br><span class="line">*/</span><br><span class="line">static int prepend_rebindings(struct rebindings_entry **rebindings_head,</span><br><span class="line"> struct rebinding rebindings[],</span><br><span class="line"> size_t nel) {</span><br><span class="line"> // 声明 rebindings_entry 一个指针,并为其分配空间</span><br><span class="line"> struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));</span><br><span class="line"> if (!new_entry) {</span><br><span class="line"> return -1;</span><br><span class="line"> }</span><br><span class="line"> // 为数组 rebindings 分配内存</span><br><span class="line"> new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);</span><br><span class="line"> if (!new_entry->rebindings) {</span><br><span class="line"> free(new_entry);</span><br><span class="line"> return -1;</span><br><span class="line"> }</span><br><span class="line"> // 内存拷贝,将 rebindings 数组中 copy 到 new_entry -> rebingdings 成员中</span><br><span class="line"> memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);</span><br><span class="line"> new_entry->rebindings_nel = nel;</span><br><span class="line"> // 头插法</span><br><span class="line"> new_entry->next = *rebindings_head;</span><br><span class="line"> *rebindings_head = new_entry;</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>经过多次操作后,结果如下图所示<br><img src="https://github.com/zhang759740844/MyImgs/blob/master/MyBlog/fishhook_11.png?raw=true" alt="rebindings_entry"></p>
<p><strong>Note</strong>: 这里 <code>*rebindings</code> 是一个数组。</p>
<h2 id="rebind-symbols-for-image"><a href="#rebind-symbols-for-image" class="headerlink" title="_rebind_symbols_for_image"></a>_rebind_symbols_for_image</h2><p><code>_dyld_register_func_for_add_image</code> 方法是 dyld 注册回调函数的方法,当镜像被加载的时候,就会主动触发注册的回调方法。</p>
<blockquote>
<p>一个可执行文件会加载非常多的动态库,每个动态库的成功加载都会触发注册的回调方法。每个动态库镜像都会根据设置重绑定符号</p>
</blockquote>
<p>这里多个 image 可以在程序运行的时候通过 <code>image list</code> 获取。</p>
<p>而这里,就注册了 <code>_rebind_symbols_for_image</code> 方法,但里面没做任何事情,直接调用另外一个方法。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923150819.png"></p>
<ul>
<li>header: 当前可执行文件的虚拟内存地址;也就是 image 的 header 头信息,结构体如下图所示<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/202209231503977.png" alt="Mach Header"><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image list</span><br><span class="line">[ 0] 0D70A5F7-C54D-312D-B242-ADE1AB9BEF9D 0x000000010d574000 /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app/Demo_fishhook </span><br><span class="line"> /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app.dSYM/Contents/Resources/DWARF/Demo_fishhook</span><br></pre></td></tr></table></figure></li>
<li>slide: ASLR 偏移值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image list -o -f</span><br><span class="line">[ 0] 0x000000000d574000 /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app/Demo_fishhook</span><br></pre></td></tr></table></figure></li>
<li>magic: 0x00000000feedfacf, 同 MachOView 那一栏的值,这里是大端模式<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 4277009103</span><br><span class="line">(long) $2 = 0x00000000feedfacf</span><br></pre></td></tr></table></figure></li>
</ul>
<p><strong>Note:</strong> 该进程有多少个 image, 该方法就会回调多少次。</p>
<h2 id="rebind-symbols-for-image-1"><a href="#rebind-symbols-for-image-1" class="headerlink" title="rebind_symbols_for_image"></a>rebind_symbols_for_image</h2><p>核心方法。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line">static void rebind_symbols_for_image(struct rebindings_entry *rebindings,</span><br><span class="line"> const struct mach_header *header,</span><br><span class="line"> intptr_t slide) {</span><br><span class="line"> Dl_info info;</span><br><span class="line"> // header 无效</span><br><span class="line"> if (dladdr(header, &info) == 0) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> // 1. 查找 linkedit_segment symtab_cmd dysymtab_cmd</span><br><span class="line"> segment_command_t *cur_seg_cmd;</span><br><span class="line"> segment_command_t *linkedit_segment = NULL;</span><br><span class="line"> struct symtab_command* symtab_cmd = NULL;</span><br><span class="line"> struct dysymtab_command* dysymtab_cmd = NULL;</span><br><span class="line"></span><br><span class="line"> // 过掉 Mach-O Header, 到 Load Commands</span><br><span class="line"> uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);</span><br><span class="line"> /*</span><br><span class="line"> 遍历每个 Load Command</span><br><span class="line"> header->ncmds: Load Commands 的数量</span><br><span class="line"> cur_seg_cmd->cmdsize: 当前 load command 的大小</span><br><span class="line"> */</span><br><span class="line"> for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {</span><br><span class="line"> cur_seg_cmd = (segment_command_t *)cur;</span><br><span class="line"> // 判断类型是否是 SEG_LINKEDIT LC_SYMTAB LC_DYSYMTAB</span><br><span class="line"> if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {</span><br><span class="line"> if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {</span><br><span class="line"> linkedit_segment = cur_seg_cmd;</span><br><span class="line"> }</span><br><span class="line"> } else if (cur_seg_cmd->cmd == LC_SYMTAB) {</span><br><span class="line"> symtab_cmd = (struct symtab_command*)cur_seg_cmd;</span><br><span class="line"> } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {</span><br><span class="line"> dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 没找到,则 return</span><br><span class="line"> if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||</span><br><span class="line"> !dysymtab_cmd->nindirectsyms) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Find base symbol/string table addresses</span><br><span class="line"> /*</span><br><span class="line"> 2. 获取相关地址值</span><br><span class="line"> slide: ASLR 值</span><br><span class="line"> vmaddr: 虚拟地址值,fileoff: 文件偏移量,两者相减即可得 VM Size.</span><br><span class="line"> linkedit_base 就是在虚拟内存加载的基地址(image list 下 image 的值/header)</span><br><span class="line"> */</span><br><span class="line"> uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;</span><br><span class="line"> nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);</span><br><span class="line"> char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);</span><br><span class="line"></span><br><span class="line"> // Get indirect symbol table (array of uint32_t indices into symbol table)</span><br><span class="line"> uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);</span><br><span class="line"> </span><br><span class="line"> // 3. 查找 section</span><br><span class="line"> // 游标重置</span><br><span class="line"> cur = (uintptr_t)header + sizeof(mach_header_t);</span><br><span class="line"> for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {</span><br><span class="line"> cur_seg_cmd = (segment_command_t *)cur;</span><br><span class="line"> if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {</span><br><span class="line"> if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&</span><br><span class="line"> strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> // __DATA segment load command 后面跟 n 个 sections</span><br><span class="line"> for (uint j = 0; j < cur_seg_cmd->nsects; j++) {</span><br><span class="line"> section_t *sect =</span><br><span class="line"> (section_t *)(cur + sizeof(segment_command_t)) + j;</span><br><span class="line"> if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {</span><br><span class="line"> perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);</span><br><span class="line"> }</span><br><span class="line"> if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {</span><br><span class="line"> perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="查找-linkedit-segment-symtab-cmd-dysymtab-cmd"><a href="#查找-linkedit-segment-symtab-cmd-dysymtab-cmd" class="headerlink" title="查找 linkedit_segment symtab_cmd dysymtab_cmd"></a>查找 linkedit_segment symtab_cmd dysymtab_cmd</h3><p>这没什么说的,就是常规的查找操作。</p>
<h4 id="Load-Commands"><a href="#Load-Commands" class="headerlink" title="Load Commands"></a>Load Commands</h4><p>见下面 <code>__LINKEDIT</code> 截图左侧的三个箭头。</p>
<h4 id="LINKEDIT"><a href="#LINKEDIT" class="headerlink" title="__LINKEDIT"></a>__LINKEDIT</h4><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923151356.png" alt="__LINKEDIT"><br>这是一个有意思的地方,<code>__DATA Section</code> 后面的都是 <code>__LINKEDIT Segment</code>,如下图。<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923151704.png" alt="__LINKEDIT Segment Start"><br><strong>Note:</strong> 这里切换成 RAW 了。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923151925.png" alt="__LINKEDIT Segment End"></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x34000 + 0x836B0 // </span><br><span class="line">(int) $5 = 0x000b76b0</span><br><span class="line">(lldb) p/x 0xB76A0 + 0x10 // 16: 0x10</span><br><span class="line">(int) $6 = 0x000b76b0</span><br></pre></td></tr></table></figure>
<p>最后一个 Section 的 index 是 0xB76A0, 占 16Bytes, 也就是 0x000b76b0,没毛病。</p>
<h4 id="LC-SYSTAB"><a href="#LC-SYSTAB" class="headerlink" title="LC_SYSTAB"></a>LC_SYSTAB</h4><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923152549.png" alt="LC_SYSTAB"><br>注意两个 Table Offset, 分别对应 Symbol Table 和 String Table 的 file offset.</p>
<h4 id="LC-DYSYSTAB"><a href="#LC-DYSYSTAB" class="headerlink" title="LC_DYSYSTAB"></a>LC_DYSYSTAB</h4><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923152650.png" alt="LC_DYSYSTAB"><br>动态符号表。</p>
<h3 id="获取相关地址值"><a href="#获取相关地址值" class="headerlink" title="获取相关地址值"></a>获取相关地址值</h3><p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923174959.png"></p>
<p>从上可知</p>
<ol>
<li>linkedit_base = header, 就是虚拟内存中加载的基地址</li>
<li>symtab, 看上面的 LC_SYSTAB 截图,<strong>Symbol Table Offset</strong>(symtab_cmd->symoff): 00039f28<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923153345.png" alt="Symbol Table"></li>
<li>strtab, 看上面的 LC_SYSTAB 截图,<strong>String Table Offset</strong>(symtab_cmd->stroff): 0005C8B0<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923153636.png" alt="String Table"></li>
<li>indirect_symtab, 看上面的 LC_DYSYSTAB 截图,IndSym Table Offset: 0005C3D8<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923153807.png" alt="Dynamic Symbol Table"></li>
</ol>
<h3 id="查找-section"><a href="#查找-section" class="headerlink" title="查找 section"></a>查找 section</h3><p>在 <code>__DATA</code> Segment, 下找到 type 为 <code>S_LAZY_SYMBOL_POINTERS/S_NON_LAZY_SYMBOL_POINTER</code> 的 section。<br>详见前面的相关截图<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131201.png" alt="__DATA,__nl_symbol_ptr Section"></p>
<h2 id="perform-rebinding-with-section"><a href="#perform-rebinding-with-section" class="headerlink" title="perform_rebinding_with_section"></a>perform_rebinding_with_section</h2><p>重新绑定的逻辑,这里以 <code>__la_symbol_ptr</code> Section 为例。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">static void perform_rebinding_with_section(struct rebindings_entry *rebindings,</span><br><span class="line"> section_t *section,</span><br><span class="line"> intptr_t slide,</span><br><span class="line"> nlist_t *symtab,</span><br><span class="line"> char *strtab,</span><br><span class="line"> uint32_t *indirect_symtab) {</span><br><span class="line"> // 该 section(__la_symbol_ptr) 在 indirect_symtab 的起始下标, 指向 indirect_symtab</span><br><span class="line"> uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;</span><br><span class="line"> // 指向具体地址,Section64(__DATA, __la_symbol_ptr)</span><br><span class="line"> void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);</span><br><span class="line"></span><br><span class="line"> /*</span><br><span class="line"> 指针格式 sizeof(void *) = 8, </span><br><span class="line"> section->size = 1128, 在 MachOView Section64 Header(__la_symbol_ptr), size 也是 1128</span><br><span class="line"> 所以共 1128/8 = 141 个符号,具体值在 Section64(__DATA, __la_symbol_ptr)</span><br><span class="line"> */</span><br><span class="line"> for (uint i = 0; i < section->size / sizeof(void *); i++) {</span><br><span class="line"> uint32_t symtab_index = indirect_symbol_indices[i];</span><br><span class="line"> // 无效的符号</span><br><span class="line"> if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||</span><br><span class="line"> symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> // 在字符串表的下标</span><br><span class="line"> uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;</span><br><span class="line"> // 获取字符串名</span><br><span class="line"> char *symbol_name = strtab + strtab_offset;</span><br><span class="line"> bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];</span><br><span class="line"> struct rebindings_entry *cur = rebindings;</span><br><span class="line"> while (cur) {</span><br><span class="line"> for (uint j = 0; j < cur->rebindings_nel; j++) {</span><br><span class="line"> // &symbol_name[1] 去掉函数修饰时前面的 `_`, eg: _NSLog</span><br><span class="line"> if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {</span><br><span class="line"> kern_return_t err;</span><br><span class="line"></span><br><span class="line"> if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement)</span><br><span class="line"> // 替换前的原函数地址</span><br><span class="line"> *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 1. Moved the vm protection modifying codes to here to reduce the</span><br><span class="line"> * changing scope.</span><br><span class="line"> * 2. Adding VM_PROT_WRITE mode unconditionally because vm_region</span><br><span class="line"> * API on some iOS/Mac reports mismatch vm protection attributes.</span><br><span class="line"> * -- Lianfu Hao Jun 16th, 2021</span><br><span class="line"> **/</span><br><span class="line"> err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);</span><br><span class="line"> if (err == KERN_SUCCESS) {</span><br><span class="line"> /**</span><br><span class="line"> * Once we failed to change the vm protection, we</span><br><span class="line"> * MUST NOT continue the following write actions!</span><br><span class="line"> * iOS 15 has corrected the const segments prot.</span><br><span class="line"> * -- Lionfore Hao Jun 11th, 2021</span><br><span class="line"> **/</span><br><span class="line"> // 修改函数指向的地方</span><br><span class="line"> indirect_symbol_bindings[i] = cur->rebindings[j].replacement;</span><br><span class="line"> }</span><br><span class="line"> goto symbol_loop;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> cur = cur->next;</span><br><span class="line"> }</span><br><span class="line"> symbol_loop:;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="SECTION-TYPE"><a href="#SECTION-TYPE" class="headerlink" title="SECTION_TYPE"></a>SECTION_TYPE</h3><p>该 header(0x000000010d574000) 会过掉三个 section </p>
<ul>
<li>0x000000010d5744b8: __nl_symbol_ptr</li>
<li>0x000000010d574508: __got</li>
<li>0x000000010d574558: __la_symbol_ptr</li>
</ul>
<p><strong>Note:</strong> 这里纠正了我之前的一个问题,以为只会处理 <code>__nl_symbol_ptr</code> 和 <code>__la_symbol_ptr</code> 这两个 section 的值,其实不是的,代码里面是判断的类型,而不是根据 Section 名字来的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS)</span><br></pre></td></tr></table></figure>
<h3 id="indirect-symtab"><a href="#indirect-symtab" class="headerlink" title="indirect_symtab"></a>indirect_symtab</h3><p>只处理 <code>__la_symbol_ptr</code> section.</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923154802.png"><br><code>0xa8=168</code>, 还记得前面的截图<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923131308.png" alt="__DATA,__la_symbol_ptr Section"><br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923153807.png" alt="Dynamic Symbol Table"><br>从 <code>Dynamic Symbol Table</code> 开始,第 168 个符号的 Offset 为 <code>0x000000000005c678</code>。<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923161044.png"><br>从这里开始遍历,共遍历 141 个符号。<br>PS: 这里 MachOView 的 Dynamic Symbol Table 已经把符号都显示出来了(见 Symbol 那一行)。</p>
<h3 id="symtab"><a href="#symtab" class="headerlink" title="symtab"></a>symtab</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) po symtab_index</span><br><span class="line">8656</span><br></pre></td></tr></table></figure>
<p>在符号表里面找第 8656 个符号。<br>PS: 这里是以 <code>NSLog</code> 来跟踪的。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923161741.png"></p>
<h3 id="strtab"><a href="#strtab" class="headerlink" title="strtab"></a>strtab</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x 0x0005C8B0 + 0x000031C5</span><br><span class="line">0x5FA75</span><br></pre></td></tr></table></figure>
<p><code>0005C8B0</code> 是前面 strtab 的起始地址。</p>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923162233.png"><br>如上图,即 0x5FA70: m, 开始数到 0x5FA75: _, 遇到 .(0x5FA7B) 就结束。这里的 Data 都是 ASCII 码来着,查看 <a href="https://www.asciitable.com/">asciitable</a>。</p>
<ul>
<li>6D: m</li>
<li>0(00): .</li>
<li>5F: _</li>
<li>4E: N</li>
</ul>
<h3 id="代码具体地址"><a href="#代码具体地址" class="headerlink" title="代码具体地址"></a>代码具体地址</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 指向具体地址,Section64(__DATA, __la_symbol_ptr)</span><br><span class="line">void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p/x slide</span><br><span class="line">(intptr_t) $52 = 0x000000000d574000</span><br><span class="line">(lldb) p/x section->addr</span><br><span class="line">(uint64_t) $53 = 0x000000010002c0d8</span><br><span class="line">(lldb) p indirect_symbol_bindings</span><br><span class="line">(void **) $54 = 0x000000010d5a00d8</span><br><span class="line">(lldb) p/x 0x000000010d5a00d8 - 0x000000010d574000</span><br><span class="line">(long) $55 = 0x000000000002c0d8</span><br></pre></td></tr></table></figure>
<p><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923162922.png"></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p 0x2c540 - 0x2c0d8</span><br><span class="line">(int) $60 = 1128</span><br><span class="line">(lldb) p 1128/8</span><br><span class="line">(int) $61 = 141</span><br></pre></td></tr></table></figure>
<p><code>0x2c540</code> 是 <code>__DATA,__mod_init_func</code> 的地址。<br>这里共 141 个数据,所以通过 Load Command 下面每个 Section Header 的描述信息可知该 Section 下面的符号大小和个数。</p>
<h3 id="替换函数地址"><a href="#替换函数地址" class="headerlink" title="替换函数地址"></a>替换函数地址</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(lldb) p &indirect_symbol_bindings[i]</span><br><span class="line">(void **) $62 = 0x000000010d5a00f0</span><br><span class="line">(lldb) p/x 0x000000010d5a00f0 - 0x000000010d574000</span><br><span class="line">(long) $63 = 0x000000000002c0f0</span><br></pre></td></tr></table></figure>
<p>就是上面截图 <code>NSLog</code> 符号在 <code>Section64(__DATA, __la_symbol_ptr)</code> 的值,因为 <code>indirect_symbol_bindings</code> 是二维指针数组,所以,这里需要进行取地址操作(&),所以这里替换 <code>Section64(__DATA, __la_symbol_ptr)</code>里面的具体地址值。</p>
<h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><ol>
<li>通过注册系统回调 <code>_dyld_register_func_for_add_image</code> 获取每个 image 的虚拟内存起始地址和 ASLR 偏移</li>
<li>根据 image 的起始地址,加上 Header 的大小(Header 固定大小为 0x20),得出 <code>SEG_LINKEDIT/LC_SYMTAB/LC_DYSYMTAB</code> 这3个 Load Commands 的起始地址</li>
<li>遍历 Load Commands,拿到 __DATA segment 里面类型为 <code>S_LAZY_SYMBOL_POINTERS/S_NON_LAZY_SYMBOL_POINTERS</code> 的 section (包括 <code>__DATA,__nl_symbol_ptr/__got/__la_symbol_ptr</code> 三个) 的各项信息,包括段的位置,段的大小,段在 Dynamic Symbol Table 的起始索引 reserved1(也就是 MachOView 中的 Indirect Sym Index)</li>
<li>在 <code>Dynamic Symbol Table</code> 遍历相关 Section(eg: <code>Section64(__DATA, __la_symbol_ptr)</code>) 的每个符号,然后找到符号在 <code>LC_SYMTAB</code> 的地址,从而得知该符号的名字</li>
<li>拿到该名字跟需要替换的符号做对比,如果对得上的话,进行替换,修改该 Section 下对应符号的指针指向</li>
</ol>
<h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1><h2 id="lldb-image"><a href="#lldb-image" class="headerlink" title="lldb - image"></a>lldb - image</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image help</span><br><span class="line">Commands for accessing information for one or more target modules.</span><br><span class="line"></span><br><span class="line">Syntax: target modules <sub-command> ...</span><br><span class="line"></span><br><span class="line">The following subcommands are supported:</span><br><span class="line"></span><br><span class="line"> add -- Add a new module to the current target's modules.</span><br><span class="line"> dump -- Commands for dumping information about one or more target</span><br><span class="line"> modules.</span><br><span class="line"> list -- List current executable and dependent shared library</span><br><span class="line"> images.</span><br><span class="line"> load -- Set the load addresses for one or more sections in a</span><br><span class="line"> target module.</span><br><span class="line"> lookup -- Look up information within executable and dependent</span><br><span class="line"> shared library images.</span><br><span class="line"> search-paths -- Commands for managing module search paths for a target.</span><br><span class="line"> show-unwind -- Show synthesized unwind instructions for a function.</span><br><span class="line"></span><br><span class="line">(lldb) help image lookup</span><br><span class="line">Look up information within executable and dependent shared library images.</span><br><span class="line"></span><br><span class="line">Syntax: target modules lookup <cmd-options> [<filename> [<filename> [...]]]</span><br><span class="line"></span><br><span class="line">Command Options Usage:</span><br><span class="line"> target modules lookup [-Av] -a <address-expression> [-o <offset>] [<filename> [<filename> [...]]]</span><br><span class="line"> target modules lookup [-Arv] -s <symbol> [<filename> [<filename> [...]]]</span><br><span class="line"> target modules lookup [-Aiv] -f <filename> [-l <linenum>] [<filename> [<filename> [...]]]</span><br><span class="line"> target modules lookup [-Airv] -F <function-name> [<filename> [<filename> [...]]]</span><br><span class="line"> target modules lookup [-Airv] -n <function-or-symbol> [<filename> [<filename> [...]]]</span><br><span class="line"> target modules lookup [-Av] -t <name> [<filename> [<filename> [...]]]</span><br><span class="line"></span><br><span class="line"> -A ( --all )</span><br><span class="line"> Print all matches, not just the best match, if a best match is</span><br><span class="line"> available.</span><br><span class="line"></span><br><span class="line"> -F <function-name> ( --function <function-name> )</span><br><span class="line"> Lookup a function by name in the debug symbols in one or more</span><br><span class="line"> target modules.</span><br><span class="line"></span><br><span class="line"> -a <address-expression> ( --address <address-expression> )</span><br><span class="line"> Lookup an address in one or more target modules.</span><br><span class="line"></span><br><span class="line"> -f <filename> ( --file <filename> )</span><br><span class="line"> Lookup a file by fullpath or basename in one or more target</span><br><span class="line"> modules.</span><br><span class="line"></span><br><span class="line"> -i ( --no-inlines )</span><br><span class="line"> Ignore inline entries (must be used in conjunction with --file or</span><br><span class="line"> --function).</span><br><span class="line"></span><br><span class="line"> -l <linenum> ( --line <linenum> )</span><br><span class="line"> Lookup a line number in a file (must be used in conjunction with</span><br><span class="line"> --file).</span><br><span class="line"></span><br><span class="line"> -n <function-or-symbol> ( --name <function-or-symbol> )</span><br><span class="line"> Lookup a function or symbol by name in one or more target modules.</span><br><span class="line"></span><br><span class="line"> -o <offset> ( --offset <offset> )</span><br><span class="line"> When looking up an address subtract <offset> from any addresses</span><br><span class="line"> before doing the lookup.</span><br><span class="line"></span><br><span class="line"> -r ( --regex )</span><br><span class="line"> The <name> argument for name lookups are regular expressions.</span><br><span class="line"></span><br><span class="line"> -s <symbol> ( --symbol <symbol> )</span><br><span class="line"> Lookup a symbol by name in the symbol tables in one or more target</span><br><span class="line"> modules.</span><br><span class="line"></span><br><span class="line"> -t <name> ( --type <name> )</span><br><span class="line"> Lookup a type by name in the debug symbols in one or more target</span><br><span class="line"> modules.</span><br><span class="line"></span><br><span class="line"> -v ( --verbose )</span><br><span class="line"> Enable verbose lookup information.</span><br><span class="line"> </span><br><span class="line"> This command takes options and free-form arguments. If your arguments</span><br><span class="line"> resemble option specifiers (i.e., they start with a - or --), you must use</span><br><span class="line"> ' -- ' between the end of the command options and the beginning of the</span><br><span class="line"> arguments.</span><br><span class="line"></span><br><span class="line">'image' is an abbreviation for 'target modules'</span><br></pre></td></tr></table></figure>
<p>比如</p>
<ol>
<li>image list: 输出当前进程所依赖的共享库</li>
<li>image list -o -f: 上个命令简洁版,输出相关库的 ASLR 地址(o: offset)</li>
<li>image lookup -n xxx: 输出 xxx 符号的相关信息</li>
<li>image lookup -t xxx: 输出 xxx 符号的类型</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image lookup -n NSLog</span><br><span class="line">1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation:</span><br><span class="line"> Address: Foundation[0x00000000000f7762] (Foundation.__TEXT.__text + 1006242)</span><br><span class="line"> Summary: Foundation`NSLog</span><br><span class="line"></span><br><span class="line">(lldb) image lookup -t FBBlockStrongRelationDetector</span><br><span class="line">0 match found in /Users/joakim/Library/Developer/Xcode/DerivedData/Demo_fishhook-ebwkhxbcogafxbclcdeifrppsgdu/Build/Products/Debug-iphonesimulator/Demo_fishhook.app/Demo_fishhook:</span><br><span class="line">id = {0x00071c41}, name = "FBBlockStrongRelationDetector", byte-size = 176, decl = FBBlockStrongRelationDetector.h:23, compiler_type = "@interface FBBlockStrongRelationDetector : NSObject{</span><br><span class="line"> void * forwarding;</span><br><span class="line"> int flags;</span><br><span class="line"> int size;</span><br><span class="line"> void (*)(_block_byref_block *, _block_byref_block *) byref_keep;</span><br><span class="line"> void (*)(_block_byref_block *) byref_dispose;</span><br><span class="line"> void *[16] captured;</span><br><span class="line"> BOOL _strong;</span><br><span class="line">}</span><br><span class="line">@property(nonatomic, assign, readwrite, getter = isStrong, setter = setStrong:) BOOL strong;</span><br><span class="line">@end"</span><br></pre></td></tr></table></figure>
<h2 id="antifishhook"><a href="#antifishhook" class="headerlink" title="antifishhook"></a>antifishhook</h2><p>从前面可知 <a href="https://github.com/facebook/fishhook">fishhook</a> 的原理就是修改相关 Section <code>__DATA,__nl_symbol_ptr/__got/__la_symbol_ptr</code> 下对应符号的指向。<br>Q: 那怎么防止 fishhook 呢?<br>A: 那在改回去呗,找到最初始的符号指向,即<br><img src="https://raw.githubusercontent.com/JoakimLiu/pic/master/fishhook/20220923124046.png"><br>然后通过 fishhook 再替换一波,使得调用 <code>NSLog</code> 的时候走 <code>__stub_helper</code> 的逻辑。<br>具体实现可参考 <a href="https://tannerjin.github.io/2019/09/25/AntiHook/">AntiHook</a>,代码是用 Swift 实现的,值得一看。</p>
<h1 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h1><ul>
<li><a href="https://book.douban.com/subject/3652388/">程序员的自我修养–链接、装载与库</a></li>
<li><a href="https://github.com/facebook/fishhook">fishhook</a></li>
<li><a href="https://sourceforge.net/projects/machoview/">MachOView</a></li>
<li><a href="https://zhang759740844.github.io/2019/07/07/fishhook/">fishhook 源码解析</a></li>
<li><a href="https://developer.apple.com/library/archive/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-command-examples.html#//apple_ref/doc/uid/TP40012917-CH3-SW5">LLDB Quick Start Guide</a></li>
<li><a href="https://tannerjin.github.io/2019/09/25/AntiHook/">AntiHook</a></li>
<li><a href="https://www.asciitable.com/">asciitable</a></li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2021/04/09/flutter-main-runApp/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="joakim.liu">
<meta itemprop="description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="牛易疯先森的开发记录">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/04/09/flutter-main-runApp/" class="post-title-link" itemprop="url">iOS Flutter Main runApp</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-04-09 18:15:05" itemprop="dateCreated datePublished" datetime="2021-04-09T18:15:05+08:00">2021-04-09</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/flutter/" itemprop="url" rel="index"><span itemprop="name">flutter</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>使用下面 demo 来探究 main 函数干了啥。</p>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line"> <span class="comment">// 见 [2.1]</span></span><br><span class="line"> runApp(</span><br><span class="line"> materialApp(),</span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyApp</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> _MyAppState createState() => _MyAppState();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_MyAppState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">MyApp</span>> </span>{</span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> Container();</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> // 因为 MaterialApp 里面的逻辑比较多,所以直接上 Container.</span></span><br><span class="line"><span class="comment"> return MaterialApp(home: Container(),);</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="2-启动流程"><a href="#2-启动流程" class="headerlink" title="2. 启动流程"></a>2. 启动流程</h1><p>调试堆栈如下</p>
<p><img src="https://sat02pap001files.storage.live.com/y4m87hRH1uo3ATjkTReKsStkU1r_iq_9l6gH1KZllL1didR5PF6jQ2eCN87_h45A2rWRMwC4zeigiNRm-_ey3jiSNCtHkHiMb-7jlhM_q6UXec8l1uVyvSEbHKzvLLtUaxFVM8Q_5Ti9paHc40DDrHDfrdRh0w23Cpj0uaMaNhoCgiEWZyvpJ5cVP8UMlw1SzKX?width=2494&height=818&cropmode=none" alt="-w1247"></p>
<h2 id="2-1-runApp"><a href="#2-1-runApp" class="headerlink" title="2.1 runApp"></a>2.1 runApp</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/widgets/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> runApp(Widget app) {</span><br><span class="line"> WidgetsFlutterBinding.ensureInitialized() <span class="comment">// 见 [2.2]</span></span><br><span class="line"> ..scheduleAttachRootWidget(app) <span class="comment">// 见 [2.4]</span></span><br><span class="line"> ..scheduleWarmUpFrame(); <span class="comment">// 见 [2.10]</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2-2-ensureInitialized"><a href="#2-2-ensureInitialized" class="headerlink" title="2.2 ensureInitialized"></a>2.2 ensureInitialized</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/widgets/binding.dart</span></span><br><span class="line"><span class="comment">/// <span class="markdown">A concrete binding for applications based on the Widgets framework.</span></span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// <span class="markdown">This is the glue that binds the framework to the Flutter engine.</span></span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WidgetsFlutterBinding</span> <span class="keyword">extends</span> <span class="title">BindingBase</span> <span class="title">with</span> <span class="title">GestureBinding</span>, <span class="title">SchedulerBinding</span>, <span class="title">ServicesBinding</span>, <span class="title">PaintingBinding</span>, <span class="title">SemanticsBinding</span>, <span class="title">RendererBinding</span>, <span class="title">WidgetsBinding</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> WidgetsBinding ensureInitialized() {</span><br><span class="line"> <span class="keyword">if</span> (WidgetsBinding.instance == <span class="keyword">null</span>)</span><br><span class="line"> WidgetsFlutterBinding(); <span class="comment">// 见 [2.2.1]</span></span><br><span class="line"> <span class="keyword">return</span> WidgetsBinding.instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-2-1-BindingBase-初始化"><a href="#2-2-1-BindingBase-初始化" class="headerlink" title="2.2.1 BindingBase 初始化"></a>2.2.1 BindingBase 初始化</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">BindingBase() {</span><br><span class="line"> <span class="comment">// 见 [2.3]</span></span><br><span class="line"> initInstances();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> </span><br><span class="line"> initServiceExtensions();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h2 id="2-3-initInstances"><a href="#2-3-initInstances" class="headerlink" title="2.3 initInstances"></a>2.3 initInstances</h2><p><code>initInstances</code> 方法的调用顺序是从右到左的,即 </p>
<ol>
<li>WidgetsBinding: The glue between the widgets layer and the Flutter engine.</li>
<li>RendererBinding: The glue between the render tree and the Flutter engine.</li>
<li>SemanticsBinding: The glue between the semantics layer and the Flutter engine.</li>
<li>PaintingBinding: Binding for the painting library.</li>
<li>ServicesBinding: Listens for platform messages and directs them to the [defaultBinaryMessenger].</li>
<li>SchedulerBinding: Scheduler for running the following: xxx.</li>
<li>GestureBinding: A binding for the gesture subsystem.</li>
</ol>
<p>按照上面的顺序执行完 <code>initInstances</code> 中的 <code>super.initInstances()</code>, 再按照上面的逆序执行各个 <code>Binding</code> 类 <code>initInstances</code> 中剩余逻辑,然后执行 <code>initServiceExtensions</code>。</p>
<h3 id="2-3-1-GestureBinding"><a href="#2-3-1-GestureBinding" class="headerlink" title="2.3.1 GestureBinding"></a>2.3.1 GestureBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/gestures/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> <span class="comment">// 设置 window 的 `onPointerDataPacket` 回调方法,在该回调方法中处理手势等交互逻辑。</span></span><br><span class="line"> <span class="built_in">window</span>.onPointerDataPacket = _handlePointerDataPacket;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="2-3-2-SchedulerBinding"><a href="#2-3-2-SchedulerBinding" class="headerlink" title="2.3.2 SchedulerBinding"></a>2.3.2 SchedulerBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/scheduler/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 非 `kReleaseMode` 模式下,添加 `addTimingsCallback` 回调,用来记录 frame 等相关信息。</span></span><br><span class="line"> <span class="keyword">if</span> (!kReleaseMode) {</span><br><span class="line"> <span class="built_in">int</span> frameNumber = <span class="number">0</span>;</span><br><span class="line"> addTimingsCallback((<span class="built_in">List</span><FrameTiming> timings) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> FrameTiming frameTiming <span class="keyword">in</span> timings) {</span><br><span class="line"> frameNumber += <span class="number">1</span>;</span><br><span class="line"> _profileFramePostEvent(frameNumber, frameTiming);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="2-3-3-ServicesBinding"><a href="#2-3-3-ServicesBinding" class="headerlink" title="2.3.3 ServicesBinding"></a>2.3.3 ServicesBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/services/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> _defaultBinaryMessenger = createBinaryMessenger();</span><br><span class="line"> _restorationManager = createRestorationManager();</span><br><span class="line"> <span class="built_in">window</span>.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;</span><br><span class="line"> initLicenses();</span><br><span class="line"> SystemChannels.system.setMessageHandler((<span class="built_in">dynamic</span> message) => handleSystemMessage(message <span class="keyword">as</span> <span class="built_in">Object</span>));</span><br><span class="line"> SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);</span><br><span class="line"> readInitialLifecycleStateFromNativeWindow();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>创建 <code>_defaultBinaryMessenger</code>,用来处理 platform channel 消息,<a href="http://joakimliu.github.io/2021/04/07/flutter-source-code-ios-platform-channels/">iOS Flutter Platform Channel</a> 有讲到。</p>
<h3 id="2-3-4-PaintingBinding"><a href="#2-3-4-PaintingBinding" class="headerlink" title="2.3.4 PaintingBinding"></a>2.3.4 PaintingBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/painting/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> <span class="comment">// 创建 image chache.</span></span><br><span class="line"> _imageCache = createImageCache();</span><br><span class="line"> shaderWarmUp?.execute();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="2-3-5-SemanticsBinding"><a href="#2-3-5-SemanticsBinding" class="headerlink" title="2.3.5 SemanticsBinding"></a>2.3.5 SemanticsBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/semantics/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> <span class="comment">// 给 _accessibilityFeatures 赋值</span></span><br><span class="line"> _accessibilityFeatures = <span class="built_in">window</span>.accessibilityFeatures;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-3-6-RendererBinding"><a href="#2-3-6-RendererBinding" class="headerlink" title="2.3.6 RendererBinding"></a>2.3.6 RendererBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/rendering/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> <span class="comment">// 见 [2.3.6.1]</span></span><br><span class="line"> _pipelineOwner = PipelineOwner(</span><br><span class="line"> onNeedVisualUpdate: ensureVisualUpdate,</span><br><span class="line"> onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,</span><br><span class="line"> onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,</span><br><span class="line"> );</span><br><span class="line"> <span class="comment">// 设置 window 的相关回调</span></span><br><span class="line"> <span class="built_in">window</span></span><br><span class="line"> ..onMetricsChanged = handleMetricsChanged</span><br><span class="line"> ..onTextScaleFactorChanged = handleTextScaleFactorChanged</span><br><span class="line"> ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged</span><br><span class="line"> ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged</span><br><span class="line"> ..onSemanticsAction = _handleSemanticsAction;</span><br><span class="line"> <span class="comment">// 见 [2.3.6.2] </span></span><br><span class="line"> initRenderView();</span><br><span class="line"> _handleSemanticsEnabledChanged();</span><br><span class="line"> <span class="keyword">assert</span>(renderView != <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 添加 addPersistentFrameCallback 回调</span></span><br><span class="line"> addPersistentFrameCallback(_handlePersistentFrameCallback);</span><br><span class="line"> initMouseTracker();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="2-3-6-1-PipelineOwner-初始化"><a href="#2-3-6-1-PipelineOwner-初始化" class="headerlink" title="2.3.6.1 PipelineOwner 初始化"></a>2.3.6.1 PipelineOwner 初始化</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The pipeline owner manages the rendering pipeline.</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PipelineOwner</span> </span>{</span><br><span class="line"> PipelineOwner({</span><br><span class="line"> <span class="keyword">this</span>.onNeedVisualUpdate,</span><br><span class="line"> <span class="keyword">this</span>.onSemanticsOwnerCreated,</span><br><span class="line"> <span class="keyword">this</span>.onSemanticsOwnerDisposed,</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>用来管理 rendering pipeline 的类,在 rendering pipeline 过程中,会按序执行以下方法(这几个阶段的操作对象都是 <code>render objects</code>)</p>
<ol>
<li>flushLayout</li>
<li>flushCompositingBits</li>
<li>flushPaint</li>
<li>flushSemantics</li>
</ol>
<h4 id="2-3-6-2-initRenderView"><a href="#2-3-6-2-initRenderView" class="headerlink" title="2.3.6.2 initRenderView"></a>2.3.6.2 initRenderView</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> initRenderView() {</span><br><span class="line"> <span class="keyword">assert</span>(!_debugIsRenderViewInitialized);</span><br><span class="line"> <span class="keyword">assert</span>(() {</span><br><span class="line"> _debugIsRenderViewInitialized = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }());</span><br><span class="line"> <span class="comment">// 创建 RenderView 对象,并赋值给 renderView, 见 [2.3.6.3]</span></span><br><span class="line"> renderView = RenderView(configuration: createViewConfiguration(), <span class="built_in">window</span>: <span class="built_in">window</span>);</span><br><span class="line"> <span class="comment">// 准备首帧</span></span><br><span class="line"> renderView.prepareInitialFrame();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="2-3-6-3-set-renderView"><a href="#2-3-6-3-set-renderView" class="headerlink" title="2.3.6.3 set renderView"></a>2.3.6.3 set renderView</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The root of the render tree.</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RenderView</span> <span class="keyword">extends</span> <span class="title">RenderObject</span> <span class="title">with</span> <span class="title">RenderObjectWithChildMixin</span><<span class="title">RenderBox</span>> </span>{</span><br><span class="line"> <span class="keyword">set</span> renderView(RenderView value) {</span><br><span class="line"> <span class="keyword">assert</span>(value != <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 见 [2.3.6.4]</span></span><br><span class="line"> _pipelineOwner.rootNode = value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-3-6-4-set-rootNode"><a href="#2-3-6-4-set-rootNode" class="headerlink" title="2.3.6.4 set rootNode"></a>2.3.6.4 set rootNode</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PipelineOwner</span> </span>{</span><br><span class="line"> <span class="keyword">set</span> rootNode(AbstractNode? value) {</span><br><span class="line"> <span class="keyword">if</span> (_rootNode == value)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> _rootNode?.detach();</span><br><span class="line"> _rootNode = value;</span><br><span class="line"> _rootNode?.attach(<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line">} </span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AbstractNode</span> </span>{</span><br><span class="line"> <span class="meta">@mustCallSuper</span></span><br><span class="line"> <span class="keyword">void</span> detach() {</span><br><span class="line"> <span class="keyword">assert</span>(_owner != <span class="keyword">null</span>);</span><br><span class="line"> _owner = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">assert</span>(parent == <span class="keyword">null</span> || attached == parent!.attached);</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="meta">@mustCallSuper</span></span><br><span class="line"> <span class="keyword">void</span> attach(<span class="keyword">covariant</span> <span class="built_in">Object</span> owner) {</span><br><span class="line"> <span class="keyword">assert</span>(owner != <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">assert</span>(_owner == <span class="keyword">null</span>);</span><br><span class="line"> _owner = owner;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>_owner 和 _rootNode 的赋值。</p>
<ul>
<li>将 RenderView 对象赋值给 _pipelineOwner 的 rootNode 成员变量</li>
<li>将 _pipelineOwner 赋值给 RenderView 对象的 _owner 成员变量</li>
</ul>
<h3 id="2-3-7-WidgetsBinding"><a href="#2-3-7-WidgetsBinding" class="headerlink" title="2.3.7 WidgetsBinding"></a>2.3.7 WidgetsBinding</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/widgets/binding.dart</span></span><br><span class="line"><span class="keyword">void</span> initInstances() {</span><br><span class="line"> <span class="keyword">super</span>.initInstances();</span><br><span class="line"> _instance = <span class="keyword">this</span>;</span><br><span class="line"> <span class="comment">// Initialization of [_buildOwner] has to be done after</span></span><br><span class="line"> <span class="comment">// [super.initInstances] is called, as it requires [ServicesBinding] to</span></span><br><span class="line"> <span class="comment">// properly setup the [defaultBinaryMessenger] instance.</span></span><br><span class="line"> _buildOwner = BuildOwner(); <span class="comment">// 见 [2.3.7.1]</span></span><br><span class="line"> buildOwner.onBuildScheduled = _handleBuildScheduled;</span><br><span class="line"> <span class="built_in">window</span>.onLocaleChanged = handleLocaleChanged;</span><br><span class="line"> <span class="built_in">window</span>.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;</span><br><span class="line"> SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);</span><br><span class="line"> FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="2-3-7-1-BuildOwner-初始化"><a href="#2-3-7-1-BuildOwner-初始化" class="headerlink" title="2.3.7.1 BuildOwner 初始化"></a>2.3.7.1 BuildOwner 初始化</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scr/widgets/framework.dart</span></span><br><span class="line"><span class="comment">/// <span class="markdown">Manager class for the widgets framework.</span></span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BuildOwner</span> </span>{</span><br><span class="line"> BuildOwner({ <span class="keyword">this</span>.onBuildScheduled });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2-4-scheduleAttachRootWidget"><a href="#2-4-scheduleAttachRootWidget" class="headerlink" title="2.4 scheduleAttachRootWidget"></a>2.4 scheduleAttachRootWidget</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> scheduleAttachRootWidget(Widget rootWidget) {</span><br><span class="line"> <span class="comment">// 异步执行</span></span><br><span class="line"> Timer.run(() {</span><br><span class="line"> <span class="comment">// 见 [2.4.1]</span></span><br><span class="line"> attachRootWidget(rootWidget);</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="2-4-1-attachRootWidget"><a href="#2-4-1-attachRootWidget" class="headerlink" title="2.4.1 attachRootWidget"></a>2.4.1 attachRootWidget</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> attachRootWidget(Widget rootWidget) {</span><br><span class="line"> _readyToProduceFrames = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 见 [2.4.2]</span></span><br><span class="line"> _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(</span><br><span class="line"> container: renderView, </span><br><span class="line"> debugShortDescription: <span class="string">'[root]'</span>, <span class="comment">// debug 描述信息</span></span><br><span class="line"> child: rootWidget, </span><br><span class="line"> ).attachToRenderTree(buildOwner, renderViewElement <span class="keyword">as</span> RenderObjectToWidgetElement<RenderBox>); <span class="comment">// 见 [2.5]</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>该方法的主要参数</p>
<ul>
<li>renderView: 见 [2.3.6.2]</li>
<li>rootWidget: 见 [1] 中的 demo, materialApp widget, 就是我们的 root widget</li>
<li>buildOwner: 见 [2.3.7.1]</li>
<li>_renderViewElement: 执行 attachToRenderTree 方法后,返回的 Element 类型的对象,可通过 <code>WidgetsBinding.renderViewElement</code> 获取,是 Element tree 的根节点(通过注释 <code>The [Element] that is at the root of the hierarchy (and which wraps the [RenderView] object at the root of the rendering hierarchy).</code>)。</li>
</ul>
<h3 id="2-4-2-RenderObjectToWidgetAdapter-初始化"><a href="#2-4-2-RenderObjectToWidgetAdapter-初始化" class="headerlink" title="2.4.2 RenderObjectToWidgetAdapter 初始化"></a>2.4.2 RenderObjectToWidgetAdapter 初始化</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A bridge from a [RenderObject] to an [Element] tree.</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RenderObjectToWidgetAdapter</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">RenderObject</span>> <span class="keyword">extends</span> <span class="title">RenderObjectWidget</span> </span>{</span><br><span class="line"> RenderObjectToWidgetAdapter({</span><br><span class="line"> <span class="keyword">this</span>.child, <span class="comment">// widget tree 中的对象</span></span><br><span class="line"> <span class="keyword">this</span>.container, <span class="comment">// The [RenderObject] that is the parent of the [Element] created by this widget.</span></span><br><span class="line"> <span class="keyword">this</span>.debugShortDescription,</span><br><span class="line"> }) : <span class="keyword">super</span>(key: GlobalObjectKey(container));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>RenderObjectToWidgetAdapter 是一个从 <code>[RenderObject]</code>(root of render tree) 到 <code>[Element]</code>(root of element tree) 的桥接类,该类的主要方法有</p>
<ul>
<li>createElement: 返回 RenderObjectToWidgetElement 对象, 见 [2.5.1]</li>
<li>createRenderObject: 返回 [2.3.6.2] 的 renderView, 见 [2.7]</li>
<li>attachToRenderTree: 创建 element, 见 [2.5]</li>
</ul>
<h2 id="2-5-attachToRenderTree"><a href="#2-5-attachToRenderTree" class="headerlink" title="2.5 attachToRenderTree"></a>2.5 attachToRenderTree</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {</span><br><span class="line"> <span class="keyword">if</span> (element == <span class="keyword">null</span>) {</span><br><span class="line"> owner.lockState(() {</span><br><span class="line"> <span class="comment">// 见 [2.5.1]</span></span><br><span class="line"> element = createElement();</span><br><span class="line"> <span class="keyword">assert</span>(element != <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 见 [2.5.2]</span></span><br><span class="line"> element.assignOwner(owner);</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">// 见 [2.5.3]</span></span><br><span class="line"> owner.buildScope(element, () {</span><br><span class="line"> <span class="comment">// 见 [2.5.4]</span></span><br><span class="line"> element.mount(<span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> });</span><br><span class="line"> SchedulerBinding.instance.ensureVisualUpdate();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> element._newWidget = <span class="keyword">this</span>;</span><br><span class="line"> element.markNeedsBuild();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> element;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>首次调用 attachToRenderTree 方法时,入参 element 为空,会创建 element 对象(RenderObjectToWidgetElement 类型),然后赋值给 [2.4.1] 的 <code>_renderViewElement</code> 成员变量。 </p>
<h3 id="2-5-1-createElement"><a href="#2-5-1-createElement" class="headerlink" title="2.5.1 createElement"></a>2.5.1 createElement</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">RenderObjectToWidgetAdapter({</span><br><span class="line"> <span class="keyword">this</span>.child,</span><br><span class="line"> <span class="keyword">this</span>.container,</span><br><span class="line"> <span class="keyword">this</span>.debugShortDescription,</span><br><span class="line">}) : <span class="keyword">super</span>(key: GlobalObjectKey(container));</span><br><span class="line"> </span><br><span class="line">RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(<span class="keyword">this</span>);</span><br></pre></td></tr></table></figure>
<p>返回 RenderObjectToWidgetElement 类型的对象。</p>
<h3 id="2-5-2-assignOwner"><a href="#2-5-2-assignOwner" class="headerlink" title="2.5.2 assignOwner"></a>2.5.2 assignOwner</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The element at the root of the tree.</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">RootRenderObjectElement</span> <span class="keyword">extends</span> <span class="title">RenderObjectElement</span> </span>{</span><br><span class="line"> <span class="keyword">void</span> assignOwner(BuildOwner owner) {</span><br><span class="line"> _owner = owner;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>跟 [2.3.6.4] 类似,都是将 <code>_pipelineOwner</code> 赋值给 <code>_owner</code> 成员变量,<code>_pipelineOwner</code> 不亏是一个管理类。</p>
<h3 id="2-5-3-buildScope"><a href="#2-5-3-buildScope" class="headerlink" title="2.5.3 buildScope"></a>2.5.3 buildScope</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> buildScope(<span class="built_in">Element</span> context, [ VoidCallback callback ]) {</span><br><span class="line"> <span class="keyword">if</span> (callback == <span class="keyword">null</span> && _dirtyElements.isEmpty)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> <span class="comment">// ... </span></span><br><span class="line"> Timeline.startSync(<span class="string">'Build'</span>, arguments: timelineArgumentsIndicatingLandmarkEvent);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> _scheduledFlushDirtyElements = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (callback != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> _dirtyElementsNeedsResorting = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> callback(); <span class="comment">// 见 [2.5.4]</span></span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 对脏 elements 进行排序</span></span><br><span class="line"> _dirtyElements.sort(<span class="built_in">Element</span>._sort);</span><br><span class="line"> _dirtyElementsNeedsResorting = <span class="keyword">false</span>;</span><br><span class="line"> <span class="built_in">int</span> dirtyCount = _dirtyElements.length;</span><br><span class="line"> <span class="built_in">int</span> index = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (index < dirtyCount) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 执行脏 element 的 rebuild 方法</span></span><br><span class="line"> _dirtyElements[index].rebuild();</span><br><span class="line"> } <span class="keyword">catch</span> (e, stack) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> index += <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {</span><br><span class="line"> _dirtyElements.sort(<span class="built_in">Element</span>._sort);</span><br><span class="line"> _dirtyElementsNeedsResorting = <span class="keyword">false</span>;</span><br><span class="line"> dirtyCount = _dirtyElements.length;</span><br><span class="line"> <span class="keyword">while</span> (index > <span class="number">0</span> && _dirtyElements[index - <span class="number">1</span>].dirty) {</span><br><span class="line"> index -= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-5-4-RenderObjectToWidgetElement-mount"><a href="#2-5-4-RenderObjectToWidgetElement-mount" class="headerlink" title="2.5.4 RenderObjectToWidgetElement.mount"></a>2.5.4 RenderObjectToWidgetElement.mount</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> mount(<span class="built_in">Element</span> parent, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="keyword">assert</span>(parent == <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 见 [2.5.4.1]</span></span><br><span class="line"> <span class="keyword">super</span>.mount(parent, newSlot);</span><br><span class="line"> <span class="comment">// 见 [2.9]</span></span><br><span class="line"> _rebuild();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>因为 RenderObjectToWidgetElement 对象是 element tree 的根节点,所以这里 parent 为 null.</p>
<h4 id="2-5-4-1-RootRenderObjectElement-mount"><a href="#2-5-4-1-RootRenderObjectElement-mount" class="headerlink" title="2.5.4.1 RootRenderObjectElement.mount"></a>2.5.4.1 RootRenderObjectElement.mount</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> mount(<span class="built_in">Element</span> parent, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="comment">// Root elements should never have parents.</span></span><br><span class="line"> <span class="keyword">assert</span>(parent == <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">assert</span>(newSlot == <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 见 [2.5.4.2]</span></span><br><span class="line"> <span class="keyword">super</span>.mount(parent, newSlot);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="2-5-4-2-RenderObjectElement-mount"><a href="#2-5-4-2-RenderObjectElement-mount" class="headerlink" title="2.5.4.2 RenderObjectElement.mount"></a>2.5.4.2 RenderObjectElement.mount</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> mount(<span class="built_in">Element</span> parent, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="comment">// 见 [2.5.4.3]</span></span><br><span class="line"> <span class="keyword">super</span>.mount(parent, newSlot);</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// 见 [2.7]</span></span><br><span class="line"> _renderObject = widget.createRenderObject(<span class="keyword">this</span>);</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// 见 [2.8]</span></span><br><span class="line"> attachRenderObject(newSlot);</span><br><span class="line"> _dirty = <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-5-4-3-Element-mount"><a href="#2-5-4-3-Element-mount" class="headerlink" title="2.5.4.3 Element.mount"></a>2.5.4.3 Element.mount</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Element</span>(Widget widget)</span><br><span class="line"> : <span class="keyword">assert</span>(widget != <span class="keyword">null</span>),</span><br><span class="line"> _widget = widget;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">void</span> mount(<span class="built_in">Element</span> parent, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// 父 element</span></span><br><span class="line"> _parent = parent;</span><br><span class="line"> <span class="comment">// 当前 element 的 slot</span></span><br><span class="line"> _slot = newSlot;</span><br><span class="line"> <span class="comment">// root element 的 _depth 为 1,子 element 依次 +1</span></span><br><span class="line"> _depth = _parent != <span class="keyword">null</span> ? _parent.depth + <span class="number">1</span> : <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 处于激活状态</span></span><br><span class="line"> _active = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 只用 parent 的 _owner, 也就是说所有 element tree 的上节点公用一个 _owner</span></span><br><span class="line"> <span class="keyword">if</span> (parent != <span class="keyword">null</span>) <span class="comment">// Only assign ownership if the parent is non-null</span></span><br><span class="line"> _owner = parent.owner;</span><br><span class="line"> <span class="comment">// widget 是构造方法中赋值的,也就是 [2.5.1] 中的 RenderObjectToWidgetAdapter, 所以 key 就是 [2.5.1] 中的 GlobalObjectKey(container), 见 [2.6]</span></span><br><span class="line"> <span class="keyword">final</span> Key key = widget.key;</span><br><span class="line"> <span class="keyword">if</span> (key <span class="keyword">is</span> GlobalKey) {</span><br><span class="line"> <span class="comment">// 见 [2.6]</span></span><br><span class="line"> key._register(<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> _updateInheritance();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2-6-GlobalKey-register"><a href="#2-6-GlobalKey-register" class="headerlink" title="2.6 GlobalKey._register"></a>2.6 GlobalKey._register</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/widgets/framework.dart</span></span><br><span class="line"><span class="meta">@optionalTypeArgs</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">GlobalKey</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">StatefulWidget</span>>> <span class="keyword">extends</span> <span class="title">Key</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> GlobalKey.constructor() : <span class="keyword">super</span>.empty();</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="built_in">Map</span><GlobalKey, <span class="built_in">Element</span>> _registry = <GlobalKey, <span class="built_in">Element</span>>{};</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 将当前 Key 对象和 Element 对象关联起来,this 就是当前 key 对象</span></span><br><span class="line"> <span class="keyword">void</span> _register(<span class="built_in">Element</span> element) {</span><br><span class="line"> _registry[<span class="keyword">this</span>] = element;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">void</span> _unregister(<span class="built_in">Element</span> element) {</span><br><span class="line"> <span class="keyword">if</span> (_registry[<span class="keyword">this</span>] == element)</span><br><span class="line"> _registry.remove(<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由 [2.5.4.3] 可知,这里是 GlobalObjectKey 类型的对象。</p>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/widgets/framework.dart</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GlobalObjectKey</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">StatefulWidget</span>>> <span class="keyword">extends</span> <span class="title">GlobalKey</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="comment">/// <span class="markdown">Creates a global key that uses [identical] on [value] for its [operator==].</span></span></span><br><span class="line"> <span class="keyword">const</span> GlobalObjectKey(<span class="keyword">this</span>.value) : <span class="keyword">super</span>.constructor();</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// <span class="markdown">The object whose identity is used by this key's [operator==].</span></span></span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">Object</span> value;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="built_in">bool</span> <span class="keyword">operator</span> ==(<span class="built_in">Object</span> other) {</span><br><span class="line"> <span class="keyword">if</span> (other.runtimeType != runtimeType)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span> other <span class="keyword">is</span> GlobalObjectKey<T></span><br><span class="line"> && identical(other.value, value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="built_in">int</span> <span class="keyword">get</span> hashCode => identityHashCode(value);</span><br><span class="line"> <span class="comment">// ... </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由 [2.5.1] 可知,value 就是 [2.3.3.2] 的 renderView 对象,说明 GlobalObjectKey 是用来处理 render tree 和 element tree 之间的节点关系。</p>
<h2 id="2-7-createRenderObject"><a href="#2-7-createRenderObject" class="headerlink" title="2.7 createRenderObject"></a>2.7 createRenderObject</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;</span><br></pre></td></tr></table></figure>
<p>将 [2.3.6.2] 的 renderView 对象赋值给 <code>_renderObject</code> 。</p>
<h2 id="2-8-attachRenderObject"><a href="#2-8-attachRenderObject" class="headerlink" title="2.8 attachRenderObject"></a>2.8 attachRenderObject</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> attachRenderObject(<span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="keyword">assert</span>(_ancestorRenderObjectElement == <span class="keyword">null</span>);</span><br><span class="line"> _slot = newSlot;</span><br><span class="line"> <span class="comment">// 选择此 element tree 的祖先节点,此时为 null.</span></span><br><span class="line"> _ancestorRenderObjectElement = _findAncestorRenderObjectElement();</span><br><span class="line"> <span class="comment">// 向 element 树中插入刚刚创建的 renderObject</span></span><br><span class="line"> _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);</span><br><span class="line"> <span class="keyword">final</span> ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();</span><br><span class="line"> <span class="keyword">if</span> (parentDataElement != <span class="keyword">null</span>)</span><br><span class="line"> _updateParentData(parentDataElement.widget);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>此时 newSlot _ancestorRenderObjectElement _slot 均为 null, 同上,后面的 _ancestorRenderObjectElement parentDataElement 也为 null.</p>
<h2 id="2-9-rebuild"><a href="#2-9-rebuild" class="headerlink" title="2.9 _rebuild"></a>2.9 _rebuild</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> _rebuild() {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 见 [2.9.1]</span></span><br><span class="line"> _child = updateChild(_child, widget.child, _rootChildSlot);</span><br><span class="line"> } <span class="keyword">catch</span> (exception, stack) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该方法的相关入参</p>
<ul>
<li>_child: 第一次执行,_child 为 null</li>
<li>widget.child: widget: RenderObjectToWidgetAdapter 对象,所以 widget.child 就是 [2.4.1] 中我们在 main.dart 中自定义的 root widget</li>
<li>_rootChildSlot: <code>static const Object _rootChildSlot = Object();</code> 是一个常量,表示一个 element 只有一个 child</li>
</ul>
<h3 id="2-9-1-Element-updateChild"><a href="#2-9-1-Element-updateChild" class="headerlink" title="2.9.1 Element.updateChild"></a>2.9.1 Element.updateChild</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Element</span> updateChild(<span class="built_in">Element</span> child, Widget newWidget, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="keyword">if</span> (newWidget == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (child != <span class="keyword">null</span>)</span><br><span class="line"> <span class="comment">// 移除旧的 element</span></span><br><span class="line"> deactivateChild(child);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">Element</span> newChild;</span><br><span class="line"> <span class="keyword">if</span> (child != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="built_in">bool</span> hasSameSuperclass = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">if</span> (hasSameSuperclass && child.widget == newWidget) {</span><br><span class="line"> <span class="keyword">if</span> (child.slot != newSlot)</span><br><span class="line"> <span class="comment">// 更新 element</span></span><br><span class="line"> updateSlotForChild(child, newSlot);</span><br><span class="line"> newChild = child;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {</span><br><span class="line"> <span class="keyword">if</span> (child.slot != newSlot)</span><br><span class="line"> <span class="comment">// 更新 element</span></span><br><span class="line"> updateSlotForChild(child, newSlot);</span><br><span class="line"> <span class="comment">// 更新 widget </span></span><br><span class="line"> child.update(newWidget);</span><br><span class="line"> newChild = child;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> newChild = inflateWidget(newWidget, newSlot);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 见 [2.9.2]</span></span><br><span class="line"> newChild = inflateWidget(newWidget, newSlot);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> newChild;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-9-2-Element-inflateWidget"><a href="#2-9-2-Element-inflateWidget" class="headerlink" title="2.9.2 Element.inflateWidget"></a>2.9.2 Element.inflateWidget</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Element</span> inflateWidget(Widget newWidget, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="keyword">final</span> Key key = newWidget.key;</span><br><span class="line"> <span class="keyword">if</span> (key <span class="keyword">is</span> GlobalKey) {</span><br><span class="line"> <span class="comment">// 从 _inactiveElements 中找可复用的 element</span></span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">Element</span> newChild = _retakeInactiveElement(key, newWidget);</span><br><span class="line"> <span class="keyword">if</span> (newChild != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 激活</span></span><br><span class="line"> newChild._activateWithParent(<span class="keyword">this</span>, newSlot);</span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">Element</span> updatedChild = updateChild(newChild, newWidget, newSlot);</span><br><span class="line"> <span class="keyword">return</span> updatedChild;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 创建 element, 见 [2.9.3], 这里是 StatefulElement</span></span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">Element</span> newChild = newWidget.createElement();</span><br><span class="line"> <span class="comment">// 见 [2.9.4]</span></span><br><span class="line"> newChild.mount(<span class="keyword">this</span>, newSlot);</span><br><span class="line"> <span class="keyword">return</span> newChild;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里 key 为 null.</p>
<h3 id="2-9-3-createElement"><a href="#2-9-3-createElement" class="headerlink" title="2.9.3 createElement"></a>2.9.3 createElement</h3><p>这个的 root widget 是 StatefulWidget 类型,所以会走</p>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">StatefulElement</span> <span class="keyword">extends</span> <span class="title">ComponentElement</span> </span>{</span><br><span class="line"> StatefulElement(StatefulWidget widget) : _state = widget.createState(), <span class="keyword">super</span>(widget) {</span><br><span class="line"> _state._element = <span class="keyword">this</span>;</span><br><span class="line"> _state._widget = widget;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的方法主要功能</p>
<ul>
<li>对 _state 赋值</li>
<li>对 element 的 widget 赋值,见前面的 <code>2.5.4.3 Element.mount</code></li>
<li>对 _state._element 赋值</li>
<li>对 _state._widget 赋值</li>
</ul>
<p>由此看见, state 是一个桥接类,关联 element 和 widget.</p>
<h3 id="2-9-4-ComponentElement-mount"><a href="#2-9-4-ComponentElement-mount" class="headerlink" title="2.9.4 ComponentElement.mount"></a>2.9.4 ComponentElement.mount</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@override</span></span><br><span class="line"><span class="keyword">void</span> mount(<span class="built_in">Element</span> parent, <span class="built_in">dynamic</span> newSlot) {</span><br><span class="line"> <span class="comment">// 见 [2.5.4.3], 不过这里 parent 有值,是 [2.5.4] 新建的 RenderObjectToWidgetElement(root element)</span></span><br><span class="line"> <span class="keyword">super</span>.mount(parent, newSlot);</span><br><span class="line"> <span class="keyword">assert</span>(_child == <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">assert</span>(_active);</span><br><span class="line"> <span class="comment">// 见 [2.9.5]</span></span><br><span class="line"> _firstBuild();</span><br><span class="line"> <span class="keyword">assert</span>(_child != <span class="keyword">null</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>StatefulElement extends ComponentElement extends Element, ComponentElement 是跟 <code>2.5.4.2 RenderObjectElement</code> 是同级别的子类。</p>
<h3 id="2-9-5-ComponentElement-firstBuild"><a href="#2-9-5-ComponentElement-firstBuild" class="headerlink" title="2.9.5 ComponentElement._firstBuild"></a>2.9.5 ComponentElement._firstBuild</h3><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> _firstBuild() {</span><br><span class="line"> <span class="comment">// 见 [2.9.5.1]</span></span><br><span class="line"> rebuild();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-9-5-1-Element-rebuild"><a href="#2-9-5-1-Element-rebuild" class="headerlink" title="2.9.5.1 Element.rebuild"></a>2.9.5.1 Element.rebuild</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> rebuild() {</span><br><span class="line"> <span class="comment">// 未被激活(没调用 mount 方法) 或者 没被标脏(没调用相关 update 方法或者 markNeedsBuild 方法),则不会走下面的逻辑</span></span><br><span class="line"> <span class="keyword">if</span> (!_active || !_dirty)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> <span class="comment">// 见 [2.9.5.2] </span></span><br><span class="line"> performRebuild();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-9-5-2-StatefulElement-performRebuild"><a href="#2-9-5-2-StatefulElement-performRebuild" class="headerlink" title="2.9.5.2 StatefulElement.performRebuild"></a>2.9.5.2 StatefulElement.performRebuild</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> performRebuild() {</span><br><span class="line"> <span class="comment">// 只有调用当前类的 didChangeDependencies 方法,才会将 _didChangeDependencies 置为 true</span></span><br><span class="line"> <span class="keyword">if</span> (_didChangeDependencies) {</span><br><span class="line"> <span class="comment">// 调用 state didChangeDependencies 方法</span></span><br><span class="line"> _state.didChangeDependencies();</span><br><span class="line"> _didChangeDependencies = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 见 [2.9.5.3]</span></span><br><span class="line"> <span class="keyword">super</span>.performRebuild();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-9-5-3-ComponentElement-performRebuild"><a href="#2-9-5-3-ComponentElement-performRebuild" class="headerlink" title="2.9.5.3 ComponentElement.performRebuild"></a>2.9.5.3 ComponentElement.performRebuild</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> performRebuild() {</span><br><span class="line"> Widget built;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 见 [2.9.5.4]</span></span><br><span class="line"> built = build();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 已 build, 后续不要再构建</span></span><br><span class="line"> _dirty = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 同 [2.9.1], 第一次 _child 为 null</span></span><br><span class="line"> _child = updateChild(_child, built, slot);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-9-5-4-build"><a href="#2-9-5-4-build" class="headerlink" title="2.9.5.4 build"></a>2.9.5.4 build</h4><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Widget build() => _state.build(<span class="keyword">this</span>);</span><br></pre></td></tr></table></figure>
<p>自此,最终会调用 rootwidget, 即 _MyAppState 的 build 方法。</p>
<h2 id="2-10-scheduleWarmUpFrame"><a href="#2-10-scheduleWarmUpFrame" class="headerlink" title="2.10 scheduleWarmUpFrame"></a>2.10 scheduleWarmUpFrame</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> scheduleWarmUpFrame() {</span><br><span class="line"> <span class="keyword">if</span> (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"> _warmUpFrame = <span class="keyword">true</span>;</span><br><span class="line"> Timeline.startSync(<span class="string">'Warm-up frame'</span>);</span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">bool</span> hadScheduledFrame = _hasScheduledFrame;</span><br><span class="line"> <span class="comment">// 异步执行, 执行完上面的代码才会执行 `2.4.1 attachRootWidget`</span></span><br><span class="line"> <span class="comment">// We use timers here to ensure that microtasks flush in between.</span></span><br><span class="line"> Timer.run(() {</span><br><span class="line"> <span class="keyword">assert</span>(_warmUpFrame);</span><br><span class="line"> handleBeginFrame(<span class="keyword">null</span>);</span><br><span class="line"> });</span><br><span class="line"> Timer.run(() {</span><br><span class="line"> <span class="keyword">assert</span>(_warmUpFrame);</span><br><span class="line"> handleDrawFrame();</span><br><span class="line"> <span class="comment">// We call resetEpoch after this frame so that, in the hot reload case,</span></span><br><span class="line"> <span class="comment">// the very next frame pretends to have occurred immediately after this</span></span><br><span class="line"> <span class="comment">// warm-up frame. The warm-up frame's timestamp will typically be far in</span></span><br><span class="line"> <span class="comment">// the past (the time of the last real frame), so if we didn't reset the</span></span><br><span class="line"> <span class="comment">// epoch we would see a sudden jump from the old time in the warm-up frame</span></span><br><span class="line"> <span class="comment">// to the new time in the "real" frame. The biggest problem with this is</span></span><br><span class="line"> <span class="comment">// that implicit animations end up being triggered at the old time and</span></span><br><span class="line"> <span class="comment">// then skipping every frame and finishing in the new time.</span></span><br><span class="line"> resetEpoch();</span><br><span class="line"> _warmUpFrame = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (hadScheduledFrame)</span><br><span class="line"> scheduleFrame();</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Lock events so touch events etc don't insert themselves until the</span></span><br><span class="line"> <span class="comment">// scheduled frame has finished.</span></span><br><span class="line"> lockEvents(() <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">await</span> endOfFrame;</span><br><span class="line"> Timeline.finishSync();</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>进行绘制操作。</p>
<h1 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h1><p>从 2.1 的 runApp 入口可知,</p>
<ol>
<li>WidgetsFlutterBinding 初始化,这是一个单例,负责创建各种绑定对象,有的时候我们需要将这段代码前置,使得各种绑定操作在 runApp 之前完成;</li>
<li>attachRootWidget, 自底向上遍历整个视图树,创建 element、renderObject 对象,建立 widget、element、renderObject 三者之间的关系;</li>
<li>scheduleWarmUpFrame, 执行绘制操作</li>
</ol>
<p>Q: 自底向上遍历整个视图树的时候,感觉 mount 无线递归执行,什么时候完成呢?<br>A: 当遍历到最顶层的 widget 时,在调用 updateChild 的时候回返回 null, 也就是<code>递</code>的结束,从而<code>归</code>回来,然后回到 <code>2.5 attachToRenderTree</code>,返回 RenderObjectToWidgetElement 类型的 root element. 我把前面 demo 的 debug 流程贴出来,如下:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">RenderObjectToWidgetElement </span><br><span class="line">- mount // parent: null, newSlot: null</span><br><span class="line"> - _rebuild</span><br><span class="line"> - updateChild // child: null, newWidget: MyApp</span><br><span class="line"> - inflateWidget // newWidget: MyApp</span><br><span class="line"> - createElement // StatefulElement: ComponentElement</span><br><span class="line"> - mount </span><br><span class="line"></span><br><span class="line">StatefulElement: ComponentElement </span><br><span class="line">- mount // parent: [root](renderObject: RenderView#5a380 NEEDS-LAYOUT NEEDS-PAINT)</span><br><span class="line"> - _firstBuild</span><br><span class="line"> - rebuild</span><br><span class="line"> - performRebuild</span><br><span class="line"> - updateChild // child: null, newWidget: Container</span><br><span class="line"> - inflateWidget // newWidget: Container</span><br><span class="line"> - createElement // StatelessElement: ComponentElement</span><br><span class="line"> - mount // </span><br><span class="line"></span><br><span class="line">StatelessElement: ComponentElement</span><br><span class="line">- mount // parent: MyApp(state: _MyAppState#9e4bc)</span><br><span class="line"> - _firstBuild</span><br><span class="line"> - rebuild</span><br><span class="line"> - performRebuild // StatelessElement.build</span><br><span class="line"> - updateChild // child: null, newWidget: LimitedBox</span><br><span class="line"> - inflateWidget // newWidget: LimitedBox(maxWidth: 0.0, maxHeight: 0.0)</span><br><span class="line"> - createElement // SingleChildRenderObjectElement: RenderObjectElement</span><br><span class="line"> - mount</span><br><span class="line"></span><br><span class="line">SingleChildRenderObjectElement</span><br><span class="line">- mount // parent: Container</span><br><span class="line"> - updateChild // child: null, newWidget: ConstrainedBox(BoxConstraints(biggest))</span><br><span class="line"> - inflateWidget // newWidget: ConstrainedBox(BoxConstraints(biggest))</span><br><span class="line"> - createElement // SingleChildRenderObjectElement: RenderObjectElement</span><br><span class="line"> - mount</span><br><span class="line"></span><br><span class="line">SingleChildRenderObjectElement</span><br><span class="line">- mount // parent: LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#3d07c NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-U</span><br><span class="line"> - updateChild // child: null, newWidget: null</span><br></pre></td></tr></table></figure>
<h2 id="3-1-问题"><a href="#3-1-问题" class="headerlink" title="3.1 问题"></a>3.1 问题</h2><ol>
<li>三棵树的关系,element 的具体功能</li>
<li>不同 element 执行 build 方法的时机</li>
<li>key slot 等联系</li>
<li>整个执行一帧的流程</li>
<li>页面刷新的具体流程,setState vs provider 局部刷新</li>
</ol>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2021/04/07/flutter-source-code-ios-platform-channels/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="joakim.liu">
<meta itemprop="description" content="你不解决问题,就会成为问题。iOS菜逗一枚。">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="牛易疯先森的开发记录">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/04/07/flutter-source-code-ios-platform-channels/" class="post-title-link" itemprop="url">iOS Flutter Platform Channel</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-04-07 18:37:37" itemprop="dateCreated datePublished" datetime="2021-04-07T18:37:37+08:00">2021-04-07</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/flutter/" itemprop="url" rel="index"><span itemprop="name">flutter</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<blockquote>
<p>本文基于 flutter 1.22.2 源码,在 ios_debug_unopt 产物下调试。</p>
</blockquote>
<h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文下面的 demo(来自 <a href="https://flutter.dev/docs/development/platform-integration/platform-channels">Writing custom platform-specific code</a>) 和 flutter 源码来分析 ios platform channels 的实现。</p>
<h2 id="1-1-demo"><a href="#1-1-demo" class="headerlink" title="1.1 demo"></a>1.1 demo</h2><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'dart:async'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:flutter/material.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:flutter/services.dart'</span>;</span><br><span class="line">...</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_MyHomePageState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">MyHomePage</span>> </span>{</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">const</span> platform = <span class="keyword">const</span> MethodChannel(<span class="string">'samples.flutter.dev/battery'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Get battery level.</span></span><br><span class="line"> <span class="built_in">String</span> _batteryLevel = <span class="string">'Unknown battery level.'</span>;</span><br><span class="line"></span><br><span class="line"> Future<<span class="keyword">void</span>> _getBatteryLevel() <span class="keyword">async</span> {</span><br><span class="line"> <span class="built_in">String</span> batteryLevel;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">int</span> result = <span class="keyword">await</span> platform.invokeMethod(<span class="string">'getBatteryLevel'</span>);</span><br><span class="line"> batteryLevel = <span class="string">'Battery level at <span class="subst">$result</span> % .'</span>;</span><br><span class="line"> } <span class="keyword">on</span> PlatformException <span class="keyword">catch</span> (e) {</span><br><span class="line"> batteryLevel = <span class="string">"Failed to get battery level: '<span class="subst">${e.message}</span>'."</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> setState(() {</span><br><span class="line"> _batteryLevel = batteryLevel;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">#import <Flutter/Flutter.h></span><br><span class="line">#import "GeneratedPluginRegistrant.h"</span><br><span class="line"></span><br><span class="line">@implementation AppDelegate</span><br><span class="line">- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {</span><br><span class="line"> FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;</span><br><span class="line"></span><br><span class="line"> FlutterMethodChannel* batteryChannel = [FlutterMethodChannel</span><br><span class="line"> methodChannelWithName:@"samples.flutter.dev/battery"</span><br><span class="line"> binaryMessenger:controller.binaryMessenger];</span><br><span class="line"></span><br><span class="line"> __weak typeof(self) weakSelf = self;</span><br><span class="line"> [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {</span><br><span class="line"> // Note: this method is invoked on the UI thread.</span><br><span class="line"> if ([@"getBatteryLevel" isEqualToString:call.method]) {</span><br><span class="line"> int batteryLevel = [weakSelf getBatteryLevel];</span><br><span class="line"> </span><br><span class="line"> if (batteryLevel == -1) {</span><br><span class="line"> result([FlutterError errorWithCode:@"UNAVAILABLE"</span><br><span class="line"> message:@"Battery info unavailable"</span><br><span class="line"> details:nil]);</span><br><span class="line"> } else {</span><br><span class="line"> result(@(batteryLevel));</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> result(FlutterMethodNotImplemented);</span><br><span class="line"> }</span><br><span class="line"> }];</span><br><span class="line"></span><br><span class="line"> [GeneratedPluginRegistrant registerWithRegistry:self];</span><br><span class="line"> return [super application:application didFinishLaunchingWithOptions:launchOptions];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (int)getBatteryLevel {</span><br><span class="line"> UIDevice* device = UIDevice.currentDevice;</span><br><span class="line"> device.batteryMonitoringEnabled = YES;</span><br><span class="line"> if (device.batteryState == UIDeviceBatteryStateUnknown) {</span><br><span class="line"> return -1;</span><br><span class="line"> } else {</span><br><span class="line"> return (int)(device.batteryLevel * 100);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="2-Dart-层"><a href="#2-Dart-层" class="headerlink" title="2. Dart 层"></a>2. Dart 层</h1><h2 id="2-1-MethodChannel-初始化"><a href="#2-1-MethodChannel-初始化" class="headerlink" title="2.1 MethodChannel 初始化"></a>2.1 MethodChannel 初始化</h2><blockquote>
<p>flutter/packages/flutter/lib/src/services/platform_channel.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// <span class="markdown">A named channel for communicating with platform plugins using asynchronous method calls.</span></span></span><br><span class="line"><span class="keyword">const</span> MethodChannel(<span class="keyword">this</span>.name, [<span class="keyword">this</span>.codec = <span class="keyword">const</span> StandardMethodCodec(), BinaryMessenger? binaryMessenger ])</span><br><span class="line"> : <span class="keyword">assert</span>(name != <span class="keyword">null</span>),</span><br><span class="line"> <span class="keyword">assert</span>(codec != <span class="keyword">null</span>),</span><br><span class="line"> _binaryMessenger = binaryMessenger;</span><br></pre></td></tr></table></figure>
<p>初始化,默认是使用标准编解码器 <code>StandardMethodCodec</code>,在发送到 <code>platform plugin</code> 要使用 <code>codec</code> 进行编码,同样 <code>platform plugin</code> 发送过来的消息要解码成 Dart 层的数据。所以,<code>platform plugin</code> 那边需要兼容 Dart 层的 <code>codec</code>。</p>
<h2 id="2-2-MessageCodec-抽象类"><a href="#2-2-MessageCodec-抽象类" class="headerlink" title="2.2 MessageCodec 抽象类"></a>2.2 MessageCodec 抽象类</h2><blockquote>
<p>flutter/packages/flutter/lib/src/services/message_codec.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// <span class="markdown">A message encoding/decoding mechanism.</span></span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">MessageCodec</span><<span class="title">T</span>> </span>{</span><br><span class="line"> ByteData? encodeMessage(T message);</span><br><span class="line"> T decodeMessage(ByteData? message);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>StandardMethodCodec</code> 实现了抽象类 <code>MessageCodec</code> 的编解码方法。</p>
<p><img src="https://sat02pap001files.storage.live.com/y4mzy8_dvuO_7jpkfRcL_zpKQPzaxo5SWf7xWupP1dN-lR4kd9U5Z-UG7VVpscyQGF7iMkN6ijMHGd6VS5xe-Oc8cJ5pn3-MDVTgn-7Ofeu5LUNkq8JbdlrhzjHkx7vSWZNXT7llmx5wNwpQdHXKPDJi8Gd0zjwISaK4eUHUZSn7WPjvJM2v-JpSKbwzi91ISeq?width=1510&height=298&cropmode=none" alt="-w755"></p>
<p>从上图可知,目标有四个类实现了 <code>MessageCodec</code> 。</p>
<ul>
<li>BinaryCodec: [MessageCodec] with unencoded binary messages represented using [ByteData]. // 没编码的原始二进制数据类型 </li>
<li>JSONMessageCodec: [MessageCodec] with UTF-8 encoded JSON messages. // utf-8 编码的 json 消息</li>
<li>StandardMessageCodec: [MessageCodec] using the Flutter standard binary encoding. // flutter 标准的二进制编码,可以参考 <a href="https://flutter.dev/docs/development/platform-integration/platform-channels">Writing custom platform-specific code</a> 的 <code>Platform channel data types support and codecs</code> 小节,当然 <code>class StandardMessageCodec implements MessageCodec<dynamic> {</code> 定义上面的注释也有解释。</li>
<li>StringCodec: [MessageCodec] with UTF-8 encoded String messages. // utf-8 编码的字符串消息 </li>
</ul>
<h2 id="2-3-invokeMethod"><a href="#2-3-invokeMethod" class="headerlink" title="2.3 invokeMethod"></a>2.3 invokeMethod</h2><blockquote>
<p>flutter/packages/flutter/lib/src/services/platform_channel.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Future<T?> invokeMethod<T>(<span class="built_in">String</span> method, [ <span class="built_in">dynamic</span> arguments ]) {</span><br><span class="line"> <span class="keyword">return</span> _invokeMethod<T>(method, missingOk: <span class="keyword">false</span>, arguments: arguments);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="meta">@optionalTypeArgs</span></span><br><span class="line"> Future<T?> _invokeMethod<T>(<span class="built_in">String</span> method, { <span class="keyword">required</span> <span class="built_in">bool</span> missingOk, <span class="built_in">dynamic</span> arguments }) <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">assert</span>(method != <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 见 2.6</span></span><br><span class="line"> <span class="keyword">final</span> ByteData? result = <span class="keyword">await</span> binaryMessenger.send(</span><br><span class="line"> name,</span><br><span class="line"> codec.encodeMethodCall(MethodCall(method, arguments)),</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">if</span> (result == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (missingOk) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> MissingPluginException(<span class="string">'No implementation found for method <span class="subst">$method</span> on channel <span class="subst">$name</span>'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 见 [6.6]</span></span><br><span class="line"> <span class="keyword">return</span> codec.decodeEnvelope(result) <span class="keyword">as</span> T;</span><br><span class="line"> } </span><br></pre></td></tr></table></figure>
<p>该方法主要功能:</p>
<ol>
<li>创建 MethodCall 对象[见 2.3.1]</li>
<li>调用 StandardMessageCodec 的 encodeMethodCall, 将 MethodCall 对象编码成 ByteData 数据[见 2.3.2]</li>
<li>调用 BinaryMessenger 来发送 ByteData 数据 [见 2.4]</li>
<li>调用 StandardMessageCodec 的 decodeEnvelope, 将发送返回后的结果进行解码</li>
</ol>
<h3 id="2-3-1-MethodCall初始化"><a href="#2-3-1-MethodCall初始化" class="headerlink" title="2.3.1 MethodCall初始化"></a>2.3.1 MethodCall初始化</h3><blockquote>
<p>flutter/packages/flutter/lib/src/services/message_codec.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MethodCall(<span class="keyword">this</span>.method, [<span class="keyword">this</span>.arguments])</span><br><span class="line"> : <span class="keyword">assert</span>(method != <span class="keyword">null</span>); <span class="comment">// 方法名,非空</span></span><br></pre></td></tr></table></figure>
<p>这里的参数就是 <code>invokeMethod</code> 方法的入参 <code>method</code> 和 <code>arguments</code> 。</p>
<h3 id="2-3-2-encodeMethodCall"><a href="#2-3-2-encodeMethodCall" class="headerlink" title="2.3.2 encodeMethodCall"></a>2.3.2 encodeMethodCall</h3><blockquote>
<p>flutter/packages/flutter/lib/src/services/message_codec.dart</p>
</blockquote>
<p>来自抽象类 <code>MethodCodec</code>, 对方法调用和结果进行编解码。</p>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// <span class="markdown">A codec for method calls and enveloped results.</span></span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">MethodCodec</span> </span>{</span><br><span class="line"> <span class="comment">/// <span class="markdown">Encodes the specified [methodCall] into binary.</span></span></span><br><span class="line"> ByteData encodeMethodCall(MethodCall methodCall);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// <span class="markdown">Decodes the specified [methodCall] from binary.</span></span></span><br><span class="line"> MethodCall decodeMethodCall(ByteData? methodCall);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// <span class="markdown">Decodes the specified result [envelope] from binary.</span></span></span><br><span class="line"> <span class="built_in">dynamic</span> decodeEnvelope(ByteData envelope);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// <span class="markdown">Encodes a successful [result] into a binary envelope.</span></span></span><br><span class="line"> ByteData encodeSuccessEnvelope(<span class="built_in">dynamic</span> result);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// <span class="markdown">Encodes an error result into a binary envelope.</span></span></span><br><span class="line"> ByteData encodeErrorEnvelope({ <span class="keyword">required</span> <span class="built_in">String</span> code, <span class="built_in">String?</span> message, <span class="built_in">dynamic</span> details});</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>flutter/packages/flutter/lib/src/services/message_codecs.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@override</span></span><br><span class="line">ByteData encodeMethodCall(MethodCall call) {</span><br><span class="line"> <span class="comment">// 创建一个写buffer, 见 [2.3.3]</span></span><br><span class="line"> <span class="keyword">final</span> WriteBuffer buffer = WriteBuffer();</span><br><span class="line"> <span class="comment">// 调用 StandardMessageCodec 的 writeValue 方法,将数据写入 buffer</span></span><br><span class="line"> messageCodec.writeValue(buffer, call.method);</span><br><span class="line"> messageCodec.writeValue(buffer, call.arguments);</span><br><span class="line"> <span class="keyword">return</span> buffer.done();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-3-3-WriteBuffer"><a href="#2-3-3-WriteBuffer" class="headerlink" title="2.3.3 WriteBuffer"></a>2.3.3 WriteBuffer</h3><blockquote>
<p>flutter/packages/flutter/lib/src/foundation/serialization.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">WriteBuffer()</span><br><span class="line"> : _buffer = Uint8Buffer(),</span><br><span class="line"> _eightBytes = ByteData(<span class="number">8</span>) {</span><br><span class="line"> _eightBytesAsList = _eightBytes.buffer.asUint8List();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ByteData done() {</span><br><span class="line"> <span class="keyword">final</span> ByteData result = _buffer!.buffer.asByteData(<span class="number">0</span>, _buffer!.lengthInBytes);</span><br><span class="line"> _buffer = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 返回最后的写入结果</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } </span><br></pre></td></tr></table></figure>
<h2 id="2-4-binaryMessenger-send"><a href="#2-4-binaryMessenger-send" class="headerlink" title="2.4 binaryMessenger.send"></a>2.4 binaryMessenger.send</h2><blockquote>
<p>flutter/packages/flutter/lib/src/services/platform_channel.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">BinaryMessenger <span class="keyword">get</span> binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;</span><br><span class="line"></span><br><span class="line">BinaryMessenger <span class="keyword">get</span> defaultBinaryMessenger {</span><br><span class="line"> <span class="keyword">assert</span>(() {</span><br><span class="line"> <span class="keyword">if</span> (ServicesBinding.instance == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> FlutterError(</span><br><span class="line"> <span class="string">'ServicesBinding.defaultBinaryMessenger was accessed before the '</span></span><br><span class="line"> <span class="string">'binding was initialized.\n'</span></span><br><span class="line"> <span class="string">"If you're running an application and need to access the binary "</span></span><br><span class="line"> <span class="string">'messenger before `runApp()` has been called (for example, during '</span></span><br><span class="line"> <span class="string">'plugin initialization), then you need to explicitly call the '</span></span><br><span class="line"> <span class="string">'`WidgetsFlutterBinding.ensureInitialized()` first.\n'</span></span><br><span class="line"> <span class="string">"If you're running a test, you can call the "</span></span><br><span class="line"> <span class="string">'`TestWidgetsFlutterBinding.ensureInitialized()` as the first line in '</span></span><br><span class="line"> <span class="string">"your test's `main()` method to initialize the binding."</span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }());</span><br><span class="line"> <span class="comment">// 见 [2.4.1]</span></span><br><span class="line"> <span class="keyword">return</span> ServicesBinding.instance!.defaultBinaryMessenger;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由于 <code>MethodChannel</code> 初始化的时候没有传 <code>BinaryMessenger</code> 对象,所以这里是用的默认值:<code>ServicesBinding.instance!.defaultBinaryMessenger</code>.</p>
<h3 id="2-4-1-sendPlatformMessage"><a href="#2-4-1-sendPlatformMessage" class="headerlink" title="2.4.1 _sendPlatformMessage"></a>2.4.1 _sendPlatformMessage</h3><blockquote>
<p>flutter/packages/flutter/lib/src/services/binding.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_DefaultBinaryMessenger</span> <span class="keyword">extends</span> <span class="title">BinaryMessenger</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> _DefaultBinaryMessenger._();</span><br><span class="line"> Future<ByteData?> _sendPlatformMessage(<span class="built_in">String</span> channel, ByteData? message) {</span><br><span class="line"> <span class="comment">// 见 [2.4.2]</span></span><br><span class="line"> <span class="keyword">final</span> Completer<ByteData?> completer = Completer<ByteData?>();</span><br><span class="line"> <span class="comment">// ui.window is accessed directly instead of using ServicesBinding.instance.window</span></span><br><span class="line"> <span class="comment">// because this method might be invoked before any binding is initialized.</span></span><br><span class="line"> <span class="comment">// This issue was reported in #27541. It is not ideal to statically access</span></span><br><span class="line"> <span class="comment">// ui.window because the Window may be dependency injected elsewhere with</span></span><br><span class="line"> <span class="comment">// a different instance. However, static access at this location seems to be</span></span><br><span class="line"> <span class="comment">// the least bad option.</span></span><br><span class="line"> <span class="comment">// 见 [2.5]</span></span><br><span class="line"> ui.<span class="built_in">window</span>.sendPlatformMessage(channel, message, (ByteData? reply) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 见 [3.1.1], 会在 engine 层保存起来</span></span><br><span class="line"> completer.complete(reply);</span><br><span class="line"> } <span class="keyword">catch</span> (exception, stack) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> completer.future;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-4-2-Completer-初始化"><a href="#2-4-2-Completer-初始化" class="headerlink" title="2.4.2 Completer 初始化"></a>2.4.2 Completer 初始化</h3><blockquote>
<p>flutter/bin/cache/pkg/sky_engine/lib/async/future.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 见 [2.4.3]</span></span><br><span class="line"><span class="keyword">factory</span> Completer() => <span class="keyword">new</span> _AsyncCompleter<T>();</span><br></pre></td></tr></table></figure>
<h3 id="2-4-3-AsyncCompleter-初始化"><a href="#2-4-3-AsyncCompleter-初始化" class="headerlink" title="2.4.3 _AsyncCompleter 初始化"></a>2.4.3 _AsyncCompleter 初始化</h3><blockquote>
<p>flutter/bin/cache/pkg/sky_engine/lib/async/future_impl.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">_Completer</span><<span class="title">T</span>> <span class="keyword">implements</span> <span class="title">Completer</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="keyword">final</span> _Future<T> future = <span class="keyword">new</span> _Future<T>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">void</span> complete([FutureOr<T>? value]);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">void</span> _completeError(<span class="built_in">Object</span> error, StackTrace stackTrace);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The future's _isComplete doesn't take into account pending completions.</span></span><br><span class="line"> <span class="comment">// We therefore use _mayComplete.</span></span><br><span class="line"> <span class="built_in">bool</span> <span class="keyword">get</span> isCompleted => !future._mayComplete;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_AsyncCompleter</span><<span class="title">T</span>> <span class="keyword">extends</span> <span class="title">_Completer</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="keyword">void</span> complete([FutureOr<T>? value]) {</span><br><span class="line"> <span class="keyword">if</span> (!future._mayComplete) <span class="keyword">throw</span> <span class="keyword">new</span> StateError(<span class="string">"Future already completed"</span>);</span><br><span class="line"> <span class="comment">// 见 [6.5]</span></span><br><span class="line"> future._asyncComplete(value == <span class="keyword">null</span> ? value <span class="keyword">as</span> <span class="built_in">dynamic</span> : value);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2-5-window-sendPlatformMessage"><a href="#2-5-window-sendPlatformMessage" class="headerlink" title="2.5 window.sendPlatformMessage"></a>2.5 window.sendPlatformMessage</h2><blockquote>
<p>flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> sendPlatformMessage(<span class="built_in">String</span> name,</span><br><span class="line"> ByteData? data,</span><br><span class="line"> PlatformMessageResponseCallback? callback) {</span><br><span class="line"> <span class="comment">// 见[2.5.1]</span></span><br><span class="line"> <span class="keyword">final</span> <span class="built_in">String?</span> error =</span><br><span class="line"> _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); </span><br><span class="line"> <span class="keyword">if</span> (error != <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> Exception(error);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 见 [3.1]</span></span><br><span class="line"><span class="built_in">String?</span> _sendPlatformMessage(<span class="built_in">String</span> name,</span><br><span class="line"> PlatformMessageResponseCallback? callback,</span><br><span class="line"> ByteData? data) native <span class="string">'PlatformConfiguration_sendPlatformMessage'</span>;</span><br></pre></td></tr></table></figure>
<p><code>_sendPlatformMessage</code> 是一个 Native 代码,会调用到引擎层。</p>
<h3 id="2-5-1-zonedPlatformMessageResponseCallback"><a href="#2-5-1-zonedPlatformMessageResponseCallback" class="headerlink" title="2.5.1 _zonedPlatformMessageResponseCallback"></a>2.5.1 _zonedPlatformMessageResponseCallback</h3><blockquote>
<p>flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart</p>
</blockquote>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// <span class="markdown">Wraps the given [callback] in another callback that ensures that the</span></span></span><br><span class="line"> <span class="comment">/// <span class="markdown">original callback is called in the zone it was registered in.</span></span></span><br><span class="line"> <span class="keyword">static</span> PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback? callback) {</span><br><span class="line"> <span class="keyword">if</span> (callback == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Store the zone in which the callback is being registered.</span></span><br><span class="line"> <span class="keyword">final</span> Zone registrationZone = Zone.current;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (ByteData? data) {</span><br><span class="line"> registrationZone.runUnaryGuarded(callback, data);</span><br><span class="line"> };</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>将当前注册回调的 <code>Zone.current</code> 存起来。</p>
<h1 id="3-engine-层"><a href="#3-engine-层" class="headerlink" title="3. engine 层"></a>3. engine 层</h1><h2 id="3-1-SendPlatformMessage"><a href="#3-1-SendPlatformMessage" class="headerlink" title="3.1 _SendPlatformMessage"></a>3.1 _SendPlatformMessage</h2><blockquote>
<p>engine/src/flutter/lib/ui/window/platform_configuration.cc</p>
</blockquote>
<p>在 flutter 源码中搜索 <code>_sendPlatformMessage</code>。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// platform_configuration.cc -L137</span></span><br><span class="line"><span class="keyword">void</span> _SendPlatformMessage(Dart_NativeArguments args) {</span><br><span class="line"> tonic::DartCallStatic(&SendPlatformMessage, args);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// platform_configuration.cc -L105</span></span><br><span class="line"><span class="function">Dart_Handle <span class="title">SendPlatformMessage</span><span class="params">(Dart_Handle window,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& name,</span></span></span><br><span class="line"><span class="function"><span class="params"> Dart_Handle callback,</span></span></span><br><span class="line"><span class="function"><span class="params"> Dart_Handle data_handle)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取当前 dart_state </span></span><br><span class="line"> UIDartState* dart_state = UIDartState::Current();</span><br><span class="line"> <span class="comment">// 没有 platform_configuration, 抛出异常</span></span><br><span class="line"> <span class="keyword">if</span> (!dart_state->platform_configuration()) {</span><br><span class="line"> <span class="keyword">return</span> tonic::ToDart(</span><br><span class="line"> <span class="string">"Platform messages can only be sent from the main isolate"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fml::RefPtr<PlatformMessageResponse> response;</span><br><span class="line"> <span class="keyword">if</span> (!Dart_IsNull(callback)) {</span><br><span class="line"> <span class="comment">// 见 [3.1.1] 和 [3.1.2]</span></span><br><span class="line"> response = fml::MakeRefCounted<PlatformMessageResponseDart>(</span><br><span class="line"> tonic::DartPersistentValue(dart_state, callback),</span><br><span class="line"> dart_state->GetTaskRunners().GetUITaskRunner());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (Dart_IsNull(data_handle)) {</span><br><span class="line"> dart_state->platform_configuration()->client()->HandlePlatformMessage(</span><br><span class="line"> fml::MakeRefCounted<PlatformMessage>(name, response));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tonic::DartByteData data(data_handle);</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">uint8_t</span>* buffer = <span class="keyword">static_cast</span><<span class="keyword">const</span> <span class="keyword">uint8_t</span>*>(data.data());</span><br><span class="line"> <span class="comment">// 见 [3.2]</span></span><br><span class="line"> dart_state->platform_configuration()->client()->HandlePlatformMessage(</span><br><span class="line"> fml::MakeRefCounted<PlatformMessage>(</span><br><span class="line"> name, <span class="built_in">std</span>::<span class="built_in">vector</span><<span class="keyword">uint8_t</span>>(buffer, buffer + data.length_in_bytes()),</span><br><span class="line"> response));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> Dart_Null();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该方法主要功能:</p>
<ol>
<li>发送平台消息,只允许在主 isolate 中发出,否则会抛出异常</li>
<li>相关参数<ol>
<li>window: Dart 层的 window</li>
<li>name: channel 名</li>
<li>callback: 见 [3.1.1] 的描述</li>
<li>data_handle: 编码后的 <code>MethodCall</code> 对象,也就是待执行的方法和参数,后续赋值给 PlatformMessage 对象的 data 成员变量</li>
</ol>
</li>
<li>创建 PlatformMessageResponseDart 对象,保存 callback 和 GetUITaskRunner</li>
<li>调用 RuntimeController 的 HandlePlatformMessage 方法,入参为 PlatformMessage, 见 [3.1.3]</li>
</ol>
<h3 id="3-1-1-DartPersistentValue"><a href="#3-1-1-DartPersistentValue" class="headerlink" title="3.1.1 DartPersistentValue"></a>3.1.1 DartPersistentValue</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// dart_persistent_value.cc -L20</span></span><br><span class="line">DartPersistentValue::DartPersistentValue(DartState* dart_state,</span><br><span class="line"> Dart_Handle value)</span><br><span class="line"> : value_(<span class="literal">nullptr</span>) {</span><br><span class="line"> Set(dart_state, value);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// dart_persistent_value.cc -L30</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">DartPersistentValue::Set</span><span class="params">(DartState* dart_state, Dart_Handle value)</span> </span>{</span><br><span class="line"> TONIC_DCHECK(is_empty());</span><br><span class="line"> dart_state_ = dart_state->GetWeakPtr();</span><br><span class="line"> value_ = Dart_NewPersistentHandle(value);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>将 <code>dart_state</code> 和 <code>callback</code> 封装成 <code>DartPersistentValue</code> 对象,这里的 <code>callback</code> 就是 [2.4] 中的 <code>completer.complete(reply);</code>,处理发送返回的数据。</p>
<h3 id="3-1-2-PlatformMessageResponseDart-初始化"><a href="#3-1-2-PlatformMessageResponseDart-初始化" class="headerlink" title="3.1.2 PlatformMessageResponseDart 初始化"></a>3.1.2 PlatformMessageResponseDart 初始化</h3><blockquote>
<p>engine/src/flutter/lib/ui/window/platform_message_response_dart.cc</p>
</blockquote>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PlatformMessageResponseDart::PlatformMessageResponseDart(</span><br><span class="line"> tonic::DartPersistentValue callback,</span><br><span class="line"> fml::RefPtr<fml::TaskRunner> ui_task_runner)</span><br><span class="line"> : callback_(<span class="built_in">std</span>::move(callback)),</span><br><span class="line"> ui_task_runner_(<span class="built_in">std</span>::move(ui_task_runner)) {}</span><br></pre></td></tr></table></figure>
<p>简单的赋初始值。</p>
<h3 id="3-1-3-PlatformMessage-初始化"><a href="#3-1-3-PlatformMessage-初始化" class="headerlink" title="3.1.3 PlatformMessage 初始化"></a>3.1.3 PlatformMessage 初始化</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// platform_message.cc -L11</span></span><br><span class="line">PlatformMessage::PlatformMessage(<span class="built_in">std</span>::<span class="built_in">string</span> channel,</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">vector</span><<span class="keyword">uint8_t</span>> data,</span><br><span class="line"> fml::RefPtr<PlatformMessageResponse> response)</span><br><span class="line"> : channel_(<span class="built_in">std</span>::move(channel)),</span><br><span class="line"> data_(<span class="built_in">std</span>::move(data)),</span><br><span class="line"> hasData_(<span class="literal">true</span>),</span><br><span class="line"> response_(<span class="built_in">std</span>::move(response)) {}</span><br></pre></td></tr></table></figure>
<p>该方法相关入参</p>
<ul>
<li>channel: channel 名</li>
<li>data: data_handle(见 [3.1] 的描述) 转换后的值</li>
<li>response: 见 [3.1.1], 对 <code>3.1.1</code> 和 <code>callback</code> 的封装</li>