├── Linked_Order_Analyze.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── Linked_Order_Analyze
├── Analyze.h
├── MMTracePCGuard.h
├── main.m
├── Util.h
├── Util.m
├── MMTracePCGuard.m
└── Analyze.m
├── LICENSE
├── .gitignore
└── README.md
/Linked_Order_Analyze.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/Analyze.h:
--------------------------------------------------------------------------------
1 | //
2 | // Analyze.h
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | //
7 |
8 | #import
9 |
10 | @interface Analyze : NSObject
11 |
12 | + (void)start;
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/MMTracePCGuard.h:
--------------------------------------------------------------------------------
1 | //
2 | // MMTracePCGuard.h
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | // Copyright © 2021 taou. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MMTracePCGuard : NSObject
12 |
13 | + (void)analyze;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | //
7 |
8 | #import
9 | #import "Analyze.h"
10 |
11 | int main(int argc, const char * argv[]) {
12 | @autoreleasepool {
13 | [Analyze start];
14 | }
15 | return 0;
16 | }
17 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/Util.h:
--------------------------------------------------------------------------------
1 | //
2 | // Util.h
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | //
7 |
8 | #import
9 |
10 | @interface Util : NSObject
11 |
12 | + (unsigned long long)decimalFromHexStr:(NSString *)hexStr;
13 |
14 | + (NSString *)hexFromDecimal:(unsigned long long)decimal;
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/Util.m:
--------------------------------------------------------------------------------
1 | //
2 | // Util.m
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | //
7 |
8 | #import "Util.h"
9 |
10 | @implementation Util
11 |
12 | + (unsigned long long)decimalFromHexStr:(NSString *)hexStr
13 | {
14 | unsigned long long result = 0;
15 | NSScanner *scanner = [NSScanner scannerWithString:hexStr];
16 |
17 | [scanner setScanLocation:0]; // bypass '#' character
18 | [scanner scanHexLongLong:&result];
19 |
20 | return result;
21 | }
22 |
23 | + (NSString *)hexFromDecimal:(unsigned long long)decimal
24 | {
25 | return [NSString stringWithFormat:@"0x%llX", decimal];
26 | }
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 李扬
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | # CocoaPods
34 | #
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38 | #
39 | # Pods/
40 | #
41 | # Add this line if you want to avoid checking in source code from the Xcode workspace
42 | # *.xcworkspace
43 |
44 | # Carthage
45 | #
46 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
47 | # Carthage/Checkouts
48 |
49 | Carthage/Build/
50 |
51 | # fastlane
52 | #
53 | # It is recommended to not store the screenshots in the git repo.
54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
55 | # For more information about the recommended setup visit:
56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
57 |
58 | fastlane/report.xml
59 | fastlane/Preview.html
60 | fastlane/screenshots/**/*.png
61 | fastlane/test_output
62 |
63 | # Code Injection
64 | #
65 | # After new code Injection tools there's a generated folder /iOSInjectionProject
66 | # https://github.com/johnno1962/injectionforxcode
67 |
68 | iOSInjectionProject/
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Linked_Order_Analyze
2 | App启动时间优化 二进制重排技术 线下量化预分析工具
3 |
4 | 目的:在没有上线之前可以分析出对App启动优化节省的大致时间,起到指导作用。(_具体能优化多少,以线上数据为准_)
5 |
6 | 建议:每隔三个月执行一次二进制重排更新,确保 PageFault 次数维持在一个较低的稳定的水平
7 |
8 | ---
9 |
10 | ### 大体思路:
11 | ```
12 | 1. 读取 Linked Map 文件,获取 __Text 代码区的起始地址和块大小,分析得到需要分配的虚拟内存页个数
13 | 2. 获取 __Text 代码区 所有symbol的地址、大小和具体符号,存入字典
14 | 3. 根据字典中的起始地址和所有symbol大小的总和,校验是否和1描述相符 (_发现有重复 symbol 和 symbol地址跳跃现象_)
15 | 4. 分析 二进制重排文件order所包含的symbol,和2中字典做映射,得到symbol对应的地址和内存占用大小
16 | 5. 分析 order文件中所包含的symbol,得到需要占用的原始虚拟内存页个数
17 | 6. 将 order文件中所包含的symbol内存占用大小相加,分析得到 重排后所占虚拟内存页个数
18 | 7. 原始虚拟内存页个数 - 重排后所占虚拟内存页个数 = 节省的虚拟内存页个数
19 | 8. 节省的虚拟内存页个数 * 每一个内存缺页中断大致的处理时间 = 节省的内存缺页中断处理总时间
20 | ```
21 |
22 | ---
23 |
24 | ### 使用之前:
25 | 1. 使用xcode编译出 linked_map.txt 和 lb.order 文件,并放在同一个目录
26 | 2. 修改下面的路径到上述两个文件的根目录
27 | ```
28 | // 链接文件和order文件根目录
29 | static NSString * const BASE_PATH = @"/Users/liyang/Desktop/1";
30 | ```
31 |
32 | ---
33 |
34 | ### 如何编译出 linked_map.txt 文件
35 |
36 | 1. 在 __Target -> Build Settings__ 下,找到 __Write Link Map File__ 来设置输出与否 , 默认是 no .
37 |
38 | 2. 修改完毕之后,__clean__ 一下,运行工程,__Products -> Show in Finder__,在mach-o文件上上层目录 __Intermediates.noindex__文件下找到一个txt文件。将其重命名为linked_map.txt
39 |
40 | ---
41 |
42 | ### 如何编译出 lb.order 文件
43 |
44 | 1. 在目标工程 __Target -> Build Settings -> Other C Flags__ 添加 __-fsanitize-coverage=func,trace-pc-guard__。
45 |
46 | 如果有swfit代码,也要在 __Other Swift Flags__ 添加 __-sanitize-coverage=func__ 和__-sanitize=undefined__
47 |
48 | (如果有源码编译的Framework也要添加这些配置。CocoaPods引入的第三方库不建议添加此配置)
49 |
50 | 2. 将 __MMTracePCGuard__ 文件放入到目标工程
51 | 3. 将 __+[MMTracePCGuard analyze]__ 方法放到目标工程认为可以启动结束的位置调用,执行 __clean->build->run__。根据自身工程复杂度的情况,等待几分钟或者十几分钟,就会在沙盒 Documents 的 temp 目录生成 lb.order 文件
52 |
53 |
54 | ---
55 |
56 | ### 最终输出示例:
57 | ```
58 | ---> 分析结果:
59 | linked map __Text(链接文件):
60 | 起始地址:0x100006A60
61 | 结束地址:0x1021E75E8
62 | 分配的虚拟内存页个数:2169
63 | order symbol(重排文件):
64 | 需要重排的符号个数:4630
65 | 分布的虚拟内存页个数:392
66 | 二进制重排后分布的虚拟内存页个数:99
67 | 内存缺页中断减少的个数:293
68 | 预估节省的时间:146ms
69 | ```
70 |
71 | ---
72 |
73 | ### 如果做反向验证
74 |
75 | 用二进制重排之后的工程,再次分别编译出 linked_map.txt 和 lb.order 文件,使用此工具再次运行检查。
76 |
77 | 可以得到如下结果
78 |
79 | ```
80 | ---> 分析结果:
81 | linked map __Text(链接文件):
82 | 起始地址:0x100006A60
83 | 结束地址:0x1021E75E8
84 | 分配的虚拟内存页个数:2169
85 | order symbol(重排文件):
86 | 需要重排的符号个数:4630
87 | 分布的虚拟内存页个数:99
88 | 二进制重排后分布的虚拟内存页个数:99
89 | 内存缺页中断减少的个数:0
90 | 预估节省的时间:0ms
91 | ```
92 |
93 | 可以看出重排后的二进制文件已经不需要再次进行重排了。
94 |
95 | 至此,二进制重排线下预评估结束
96 |
97 | ---
98 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/MMTracePCGuard.m:
--------------------------------------------------------------------------------
1 | //
2 | // MMTracePCGuard.m
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | // Copyright © 2021 taou. All rights reserved.
7 | //
8 |
9 | #import "MMTracePCGuard.h"
10 | #import
11 | #import
12 |
13 | void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
14 | uint32_t *stop) {
15 | static uint64_t N; // Counter for the guards.
16 | if (start == stop || *start) return; // Initialize only once.
17 | printf("INIT: %p %p\n", start, stop);
18 | for (uint32_t *x = start; x < stop; x++)
19 | *x = ++N; // Guards should start from 1.
20 | }
21 |
22 | //原子队列
23 | static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
24 | //定义符号结构体
25 | typedef struct{
26 | void * pc;
27 | void * next;
28 | }SymbolNode;
29 |
30 | void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
31 | //if (!*guard) return; // Duplicate the guard check.
32 |
33 | void *PC = __builtin_return_address(0);
34 |
35 | SymbolNode * node = malloc(sizeof(SymbolNode));
36 | *node = (SymbolNode){PC,NULL};
37 |
38 | //入队
39 | // offsetof 用在这里是为了入队添加下一个节点找到 前一个节点next指针的位置
40 | OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
41 | }
42 |
43 | //void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
44 | // if (!*guard) return; // Duplicate the guard check.
45 | //
46 | // void *PC = __builtin_return_address(0);
47 | // Dl_info info;
48 | // dladdr(PC, &info);
49 | //
50 | // printf("-=-=-=-=-=-=-=-=nsname=%s\n\n",info.dli_sname);
51 | //
52 | //// char PcDescr[1024];
53 | //// printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
54 | //}
55 |
56 | @implementation MMTracePCGuard
57 |
58 | + (void)analyze
59 | {
60 | NSMutableArray * symbolNames = [NSMutableArray array];
61 | while (true) {
62 | //offsetof 就是针对某个结构体找到某个属性相对这个结构体的偏移量
63 | SymbolNode * node = OSAtomicDequeue(&symboList, offsetof(SymbolNode, next));
64 | if (node == NULL) break;
65 | Dl_info info;
66 | dladdr(node->pc, &info);
67 |
68 | NSString * name = @(info.dli_sname);
69 |
70 | // 添加 _
71 | BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
72 | NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
73 |
74 | //去重
75 | if (![symbolNames containsObject:symbolName]) {
76 | [symbolNames addObject:symbolName];
77 | }
78 | }
79 |
80 | //取反
81 | NSArray * symbolAry = [[symbolNames reverseObjectEnumerator] allObjects];
82 | NSLog(@"trace pc guard:\n---------------------------------------------------------------------------------\n");
83 | NSLog(@"%@",symbolAry);
84 | NSLog(@"---------------------------------------------------------------------------------");
85 |
86 | //将结果写入到文件
87 | NSString * funcString = [symbolAry componentsJoinedByString:@"\n"];
88 | NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"lb.order"];
89 | NSData * fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];
90 | BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
91 | if (result) {
92 | NSLog(@"%@",filePath);
93 | }else{
94 | NSLog(@"文件写入出错");
95 | }
96 | }
97 |
98 | @end
99 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze/Analyze.m:
--------------------------------------------------------------------------------
1 | //
2 | // Analyze.m
3 | // Linked_Order_Analyze
4 | //
5 | // Created by 李扬 on 2021/1/17.
6 | //
7 |
8 | #import "Analyze.h"
9 | #import "Util.h"
10 |
11 | /*
12 | 二进制重排技术 线下量化分析总体思路:
13 | 1. 读取 Linked Map 文件,获取 __Text 代码区的起始地址和块大小,分析得到需要分配的虚拟内存页个数
14 | 2. 获取 __Text 代码区 所有symbol的地址、大小和具体符号,存入字典
15 | 3. 根据字典中的起始地址和所有symbol大小的总和,校验是否和1描述相符 (发现有重复 symbol 和 symbol地址跳跃现象)
16 | 4. 分析 二进制重排文件order所包含的symbol,和2中字典做映射,得到order中symbol对应的地址和内存占用大小
17 | 5. 分析 order文件中所包含的symbol,得到需要占用的原始虚拟内存页个数
18 | 6. 将 order文件中所包含的symbol内存占用大小相加,分析得到重排后所占虚拟内存页个数
19 | 7. 原始虚拟内存页个数 - 重排后所占虚拟内存页个数 = 节省的虚拟内存页个数
20 | 8. 节省的虚拟内存页个数 * 每一个内存缺页中断大致的处理时间 = 节省的内存缺页中断处理总时间
21 | */
22 | // 链接文件和order文件根目录 注意:换成自己的路径字符串
23 | static NSString * const BASE_PATH = @"/Users/liyang/Desktop/1";
24 | // 链接文件名
25 | static NSString * const LINKED_MAP = @"linked_map.txt";
26 | // order 文件名
27 | static NSString * const LB_ORDER = @"lb.order";
28 | // iOS平台虚拟内存页大小(16k)
29 | static unsigned long long const VM_PAGE_SIZE_LINK = 16 * 1024;
30 |
31 | // 链接文件 __Text 映射字典 key:symbol value:[起始地址, 占用内存大小]
32 | static NSMutableDictionary *> *linkedTextDictM;
33 | // 排序文件 __Text 映射字典 key:symbol value:[起始地址, 占用内存大小]
34 | static NSMutableDictionary *> *orderTextDictM;
35 |
36 | // 处理每个内存缺页中断所需要的时间:0.1ms ~ 1ms之间,取中位数 0.5ms
37 | static double const PAGE_FAULT_CONST_ESTIMATE_TIME = 0.5;
38 |
39 | @implementation Analyze
40 |
41 | + (void)start
42 | {
43 | // 链接文件完整路径
44 | NSString *linked_map_path = [NSString stringWithFormat:@"%@/%@", BASE_PATH, LINKED_MAP];
45 | // 排序文件完整路径
46 | NSString *order_path = [NSString stringWithFormat:@"%@/%@", BASE_PATH, LB_ORDER];
47 |
48 | // 链接文件内容
49 | NSError *linkedMapContentError;
50 | NSString* linkedMapFileContents = [NSString stringWithContentsOfFile:linked_map_path encoding:NSASCIIStringEncoding error:&linkedMapContentError];
51 | if (linkedMapContentError)
52 | {
53 | abort();
54 | return;
55 | };
56 |
57 | // __text 开始地址 十进制
58 | __block unsigned long long sectionTextStartAddressDecimalValue = 0;
59 | // __text 结束地址 十进制
60 | __block unsigned long long sectionTextEndAddressDecimalValue = 0;
61 | // __Text 所占用虚拟内存页个数
62 | __block unsigned long long sectionTextLinkedVMPageCount = 0;
63 |
64 | // 1. 分析 linked map 文件,得出分配多少个虚拟内存页;section __Text __text 开始地址 十进制;section __Text __text 结束地址 十进制
65 | NSRegularExpression *regexLinkedMap = [NSRegularExpression
66 | regularExpressionWithPattern:@"(0x[A-F\\d]*)\\s+(0x[A-F\\d]*)\\s+__TEXT\\s+__text"
67 | options:0
68 | error:nil];
69 | [regexLinkedMap enumerateMatchesInString:linkedMapFileContents options:0 range:NSMakeRange(0, linkedMapFileContents.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
70 |
71 | if ([result numberOfRanges] != 3) return;
72 |
73 | // 分析 linked_map 文件,判断出虚拟内存页个数
74 | // 匹配的 section __Text __text 字符串行
75 | NSString *sectionTextLine = [linkedMapFileContents substringWithRange:result.range];
76 | NSLog(@"----__Text:%@", sectionTextLine);
77 |
78 | // 匹配 section __Text __text 首地址
79 | NSString *sectionTextStartAddressHexStr = [linkedMapFileContents substringWithRange:[result rangeAtIndex:1]];
80 | sectionTextStartAddressDecimalValue = [Util decimalFromHexStr:sectionTextStartAddressHexStr];
81 | // 匹配 section __Text __text 内存映射大小
82 | NSString *sectionTextSizeHexStr = [linkedMapFileContents substringWithRange:[result rangeAtIndex:2]];
83 |
84 | // 分析分配的虚拟内存页个数
85 | unsigned long long section_text_decimal_size = [Util decimalFromHexStr:sectionTextSizeHexStr];
86 | sectionTextLinkedVMPageCount = section_text_decimal_size / VM_PAGE_SIZE_LINK + 1;
87 |
88 | NSLog(@"----linked __text vm count:%llu", sectionTextLinkedVMPageCount);
89 |
90 | sectionTextEndAddressDecimalValue = sectionTextStartAddressDecimalValue + section_text_decimal_size;
91 | }];
92 |
93 | // 2. 分析 linked map 文件,在开始地址和结束地址之间额symble字典
94 | // __Text 所有 symbol 占用虚拟内存大小总和,发现有symbol重复和地址跳跃的现象
95 | __block unsigned long long linkedTextSumAllSymbolSize = 0;
96 | // __Text 所有 symbol 个数,和 sumAllSymbolSize 一起反推 1 中数据的正确性。个数是一直的
97 | __block unsigned long long linkedTextSymbolLineCount = 0;
98 |
99 | // __Text symbol 重复的情况
100 | __block NSMutableArray *linkedTextSymbolDuplicatedArrM = [NSMutableArray array];
101 | // 预计算下一行 symbol 起始地址,以确定地址跳跃情况
102 | __block unsigned long long linkedTextNextSymbolPreComputeStartDecialValue = 0;
103 | // __Text symbol start + size != next start 地址跳跃情况
104 | __block NSMutableArray *linkedTextSymbolStartAddressJumpedArrM = [NSMutableArray array];
105 |
106 | linkedTextDictM = [NSMutableDictionary dictionary];
107 | NSRegularExpression *regexLinkedMapLines = [NSRegularExpression
108 | regularExpressionWithPattern:@"(0x[A-F\\d]*)\\s+(0x[A-F\\d]*)\\s+\\[.*\\] (.+)"
109 | options:0
110 | error:nil];
111 | [regexLinkedMapLines enumerateMatchesInString:linkedMapFileContents options:0 range:NSMakeRange(0, linkedMapFileContents.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
112 |
113 | if ([result numberOfRanges] != 4) return;
114 |
115 | // 匹配的 symbol __Text 一行行的字符串
116 | NSString *linkedTextSymbolLine = [linkedMapFileContents substringWithRange:result.range];
117 |
118 | // symble 地址
119 | NSString *linkedTextSymbolStartAddressHexStr = [linkedMapFileContents substringWithRange:[result rangeAtIndex:1]];
120 | unsigned long long linkedTextSymbolStartAddressDecimalValue = [Util decimalFromHexStr:linkedTextSymbolStartAddressHexStr];
121 |
122 | // symbol 大小
123 | NSString *linkedTextSymbolSizeHexStr = [linkedMapFileContents substringWithRange:[result rangeAtIndex:2]];
124 | unsigned long long linkedTextSymbolSizeDecimalValue = [Util decimalFromHexStr:linkedTextSymbolSizeHexStr];
125 | // 具体的 symbol
126 | NSString *symbol = [linkedMapFileContents substringWithRange:[result rangeAtIndex:3]];
127 |
128 | // 地址在 section __Text 之间
129 | if (linkedTextSymbolStartAddressDecimalValue >= sectionTextStartAddressDecimalValue && linkedTextSymbolStartAddressDecimalValue < sectionTextEndAddressDecimalValue)
130 | {
131 | NSLog(@"----linked_symbol:%@", linkedTextSymbolLine);
132 |
133 | // 计算 linked __Text 符号所占用虚拟内存页大小 用来和 1 做反向验证
134 | linkedTextSumAllSymbolSize += linkedTextSymbolSizeDecimalValue;
135 | // 计算 linked __Text 符号个数 用来和 1 做反向验证
136 | linkedTextSymbolLineCount++;
137 |
138 | // 地址跳跃检测和记录
139 | if (linkedTextNextSymbolPreComputeStartDecialValue != 0 && linkedTextNextSymbolPreComputeStartDecialValue != linkedTextSymbolStartAddressDecimalValue)
140 | {
141 | [linkedTextSymbolStartAddressJumpedArrM addObject:linkedTextSymbolLine ?: @""];
142 | }
143 | linkedTextNextSymbolPreComputeStartDecialValue = linkedTextSymbolStartAddressDecimalValue + linkedTextSymbolSizeDecimalValue;
144 |
145 | // 符号重复 忽略
146 | if (linkedTextDictM[symbol] != nil)
147 | {
148 | [linkedTextSymbolDuplicatedArrM addObject:symbol];
149 | return;
150 | }
151 |
152 | // 存入字典
153 | [linkedTextDictM setValue:@[@(linkedTextSymbolStartAddressDecimalValue), @(linkedTextSymbolSizeDecimalValue)] forKey:symbol];
154 | }
155 | }];
156 |
157 | // 3. 根据字典中起始地址和所有symbol大小总和,校验是否和1描述相符
158 | // 个数相符,但发现有symbol重复和地址跳跃的现象
159 |
160 | // 4. 分析 二进制重排文件order所包含的symbol,和 2 中字典做映射,得到symbol对应的地址和内存占用大小
161 | // 5. 分析 order文件中所包含的symbol,得到占用原始虚拟内存页个数
162 | orderTextDictM = [NSMutableDictionary dictionary];
163 | // order 中symbol的个数
164 | __block unsigned long long orderSymbolLineCount = 0;
165 | // order 中symbol所占用内存页分布情况
166 | __block NSMutableSet *orderVMPageSetM = [NSMutableSet set];
167 | // order 中 所有 symbol 占用大小总和,除以 VM_PAGE_SIZE_LINK 可以得到排序后占用的连续虚拟内页个数
168 | __block unsigned long long orderSumAllSymbolSize = 0;
169 | // order 中 未命中 section __Text 的symbol集合
170 | __block NSMutableArray *orderUntargetSymbolsArrM = [NSMutableArray array];
171 |
172 | // order 文件内容
173 | NSError *orderContentError = nil;
174 | NSString* orderpFileContents = [NSString stringWithContentsOfFile:order_path encoding:NSASCIIStringEncoding error:&orderContentError];
175 | if (orderContentError)
176 | {
177 | abort();
178 | return;
179 | };
180 |
181 | NSRegularExpression *regexOrder = [NSRegularExpression
182 | regularExpressionWithPattern:@"(.+)"
183 | options:0
184 | error:nil];
185 | [regexOrder enumerateMatchesInString:orderpFileContents options:0 range:NSMakeRange(0, orderpFileContents.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
186 | if ([result numberOfRanges] != 2) return;
187 |
188 | // 得到 order 一行行 symbol 符号
189 | NSString *orderSymbolLine = [orderpFileContents substringWithRange:[result rangeAtIndex:1]];
190 |
191 | NSArray *arr = linkedTextDictM[orderSymbolLine];
192 | if (arr == nil)
193 | {
194 | // order 对应 linked 文件,未命中的情况
195 | [orderUntargetSymbolsArrM addObject:orderSymbolLine ?: @""];
196 | return;
197 | }
198 |
199 | // 计算 order symbol 行数
200 | orderSymbolLineCount++;
201 |
202 | // 获得 symbole 十进制 的地址
203 | unsigned long long orderSymbolStartAddressDecimalValue = [arr.firstObject unsignedLongLongValue];
204 | if (orderSymbolStartAddressDecimalValue < sectionTextStartAddressDecimalValue || sectionTextStartAddressDecimalValue > sectionTextEndAddressDecimalValue)
205 | {
206 | // 发现非法地址,不在 __Text 起始地址和终止地址之间
207 | abort();
208 | return;
209 | }
210 | unsigned long long orderSymbolRelativeStartAddressDecimalValue = orderSymbolStartAddressDecimalValue - sectionTextStartAddressDecimalValue;
211 | // 得到当前 symbole 所在的内存页序号index
212 | unsigned long long orderSymbolVMPageIndex = orderSymbolRelativeStartAddressDecimalValue / VM_PAGE_SIZE_LINK;
213 | [orderVMPageSetM addObject:@(orderSymbolVMPageIndex)]; // 得到被分配到了哪些虚拟内存页
214 |
215 | // 获得 symbole 十进制 的占用内存大小
216 | unsigned long long orderSymbolSizeDecimalValue = [arr.lastObject unsignedLongLongValue];
217 | orderSumAllSymbolSize += orderSymbolSizeDecimalValue;
218 | }];
219 |
220 | // 6. 将 order文件中所包含的symbol内存占用相加,分析得到 重排后所占虚拟内存页个数
221 | unsigned long long orderSymbolAllVMPageCount = orderSumAllSymbolSize / VM_PAGE_SIZE_LINK + 1;
222 | if (orderSymbolAllVMPageCount > orderVMPageSetM.count) orderSymbolAllVMPageCount = orderVMPageSetM.count;
223 |
224 | printf("\n---> 分析结果:");
225 | printf("\n linked map __Text(链接文件):");
226 | printf("\n 起始地址:%s", [[Util hexFromDecimal:sectionTextStartAddressDecimalValue] UTF8String]);
227 | printf("\n 结束地址:%s", [[Util hexFromDecimal:sectionTextEndAddressDecimalValue] UTF8String]);
228 | printf("\n 分配的虚拟内存页个数:%llu", sectionTextLinkedVMPageCount);
229 | printf("\n order symbol(重排文件):");
230 | printf("\n 需要重排的符号个数:%llu", orderSymbolLineCount);
231 | printf("\n 分布的虚拟内存页个数:%lu", (unsigned long)orderVMPageSetM.count);
232 | printf("\n 二进制重排后分布的虚拟内存页个数:%llu", orderSymbolAllVMPageCount);
233 | // 7. 原始虚拟内存页个数 - 重排后所占虚拟内存页个数 = 节省的虚拟内存页个数
234 | printf("\n 内存缺页中断减少的个数:%llu", orderVMPageSetM.count - orderSymbolAllVMPageCount);
235 | // 8. 节省的虚拟内存页个数 * 每一个内存缺页中断大致的处理时间 = 节省的内存缺页中断处理总时间
236 | printf("\n 预估节省的时间:%.0fms\n", (orderVMPageSetM.count - orderSymbolAllVMPageCount) * PAGE_FAULT_CONST_ESTIMATE_TIME);
237 | }
238 |
239 | @end
240 |
--------------------------------------------------------------------------------
/Linked_Order_Analyze.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B6313D6F25B4341C0035448B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B6313D6E25B4341C0035448B /* main.m */; };
11 | B6313D7825B434280035448B /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = B6313D7725B434280035448B /* Util.m */; };
12 | B6313D7C25B4348C0035448B /* Analyze.m in Sources */ = {isa = PBXBuildFile; fileRef = B6313D7B25B4348C0035448B /* Analyze.m */; };
13 | B6856E142696E8E800ADAB16 /* MMTracePCGuard.m in Sources */ = {isa = PBXBuildFile; fileRef = B6856E132696E8E800ADAB16 /* MMTracePCGuard.m */; };
14 | /* End PBXBuildFile section */
15 |
16 | /* Begin PBXCopyFilesBuildPhase section */
17 | B6313D6925B4341C0035448B /* CopyFiles */ = {
18 | isa = PBXCopyFilesBuildPhase;
19 | buildActionMask = 2147483647;
20 | dstPath = /usr/share/man/man1/;
21 | dstSubfolderSpec = 0;
22 | files = (
23 | );
24 | runOnlyForDeploymentPostprocessing = 1;
25 | };
26 | /* End PBXCopyFilesBuildPhase section */
27 |
28 | /* Begin PBXFileReference section */
29 | B6313D6B25B4341C0035448B /* Linked_Order_Analyze */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Linked_Order_Analyze; sourceTree = BUILT_PRODUCTS_DIR; };
30 | B6313D6E25B4341C0035448B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
31 | B6313D7625B434280035448B /* Util.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = ""; };
32 | B6313D7725B434280035448B /* Util.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Util.m; sourceTree = ""; };
33 | B6313D7A25B4348C0035448B /* Analyze.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Analyze.h; sourceTree = ""; };
34 | B6313D7B25B4348C0035448B /* Analyze.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Analyze.m; sourceTree = ""; };
35 | B6856E122696E8E800ADAB16 /* MMTracePCGuard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMTracePCGuard.h; sourceTree = ""; };
36 | B6856E132696E8E800ADAB16 /* MMTracePCGuard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMTracePCGuard.m; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | B6313D6825B4341C0035448B /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | B6313D6225B4341C0035448B = {
51 | isa = PBXGroup;
52 | children = (
53 | B6313D6D25B4341C0035448B /* Linked_Order_Analyze */,
54 | B6313D6C25B4341C0035448B /* Products */,
55 | );
56 | sourceTree = "";
57 | };
58 | B6313D6C25B4341C0035448B /* Products */ = {
59 | isa = PBXGroup;
60 | children = (
61 | B6313D6B25B4341C0035448B /* Linked_Order_Analyze */,
62 | );
63 | name = Products;
64 | sourceTree = "";
65 | };
66 | B6313D6D25B4341C0035448B /* Linked_Order_Analyze */ = {
67 | isa = PBXGroup;
68 | children = (
69 | B6856E122696E8E800ADAB16 /* MMTracePCGuard.h */,
70 | B6856E132696E8E800ADAB16 /* MMTracePCGuard.m */,
71 | B6313D6E25B4341C0035448B /* main.m */,
72 | B6313D7A25B4348C0035448B /* Analyze.h */,
73 | B6313D7B25B4348C0035448B /* Analyze.m */,
74 | B6313D7625B434280035448B /* Util.h */,
75 | B6313D7725B434280035448B /* Util.m */,
76 | );
77 | path = Linked_Order_Analyze;
78 | sourceTree = "";
79 | };
80 | /* End PBXGroup section */
81 |
82 | /* Begin PBXNativeTarget section */
83 | B6313D6A25B4341C0035448B /* Linked_Order_Analyze */ = {
84 | isa = PBXNativeTarget;
85 | buildConfigurationList = B6313D7225B4341C0035448B /* Build configuration list for PBXNativeTarget "Linked_Order_Analyze" */;
86 | buildPhases = (
87 | B6313D6725B4341C0035448B /* Sources */,
88 | B6313D6825B4341C0035448B /* Frameworks */,
89 | B6313D6925B4341C0035448B /* CopyFiles */,
90 | );
91 | buildRules = (
92 | );
93 | dependencies = (
94 | );
95 | name = Linked_Order_Analyze;
96 | productName = Linked_Order_Analyze;
97 | productReference = B6313D6B25B4341C0035448B /* Linked_Order_Analyze */;
98 | productType = "com.apple.product-type.tool";
99 | };
100 | /* End PBXNativeTarget section */
101 |
102 | /* Begin PBXProject section */
103 | B6313D6325B4341C0035448B /* Project object */ = {
104 | isa = PBXProject;
105 | attributes = {
106 | LastUpgradeCheck = 1230;
107 | TargetAttributes = {
108 | B6313D6A25B4341C0035448B = {
109 | CreatedOnToolsVersion = 12.3;
110 | };
111 | };
112 | };
113 | buildConfigurationList = B6313D6625B4341C0035448B /* Build configuration list for PBXProject "Linked_Order_Analyze" */;
114 | compatibilityVersion = "Xcode 9.3";
115 | developmentRegion = en;
116 | hasScannedForEncodings = 0;
117 | knownRegions = (
118 | en,
119 | Base,
120 | );
121 | mainGroup = B6313D6225B4341C0035448B;
122 | productRefGroup = B6313D6C25B4341C0035448B /* Products */;
123 | projectDirPath = "";
124 | projectRoot = "";
125 | targets = (
126 | B6313D6A25B4341C0035448B /* Linked_Order_Analyze */,
127 | );
128 | };
129 | /* End PBXProject section */
130 |
131 | /* Begin PBXSourcesBuildPhase section */
132 | B6313D6725B4341C0035448B /* Sources */ = {
133 | isa = PBXSourcesBuildPhase;
134 | buildActionMask = 2147483647;
135 | files = (
136 | B6313D7C25B4348C0035448B /* Analyze.m in Sources */,
137 | B6313D6F25B4341C0035448B /* main.m in Sources */,
138 | B6856E142696E8E800ADAB16 /* MMTracePCGuard.m in Sources */,
139 | B6313D7825B434280035448B /* Util.m in Sources */,
140 | );
141 | runOnlyForDeploymentPostprocessing = 0;
142 | };
143 | /* End PBXSourcesBuildPhase section */
144 |
145 | /* Begin XCBuildConfiguration section */
146 | B6313D7025B4341C0035448B /* Debug */ = {
147 | isa = XCBuildConfiguration;
148 | buildSettings = {
149 | ALWAYS_SEARCH_USER_PATHS = NO;
150 | CLANG_ANALYZER_NONNULL = YES;
151 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
152 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
153 | CLANG_CXX_LIBRARY = "libc++";
154 | CLANG_ENABLE_MODULES = YES;
155 | CLANG_ENABLE_OBJC_ARC = YES;
156 | CLANG_ENABLE_OBJC_WEAK = YES;
157 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
158 | CLANG_WARN_BOOL_CONVERSION = YES;
159 | CLANG_WARN_COMMA = YES;
160 | CLANG_WARN_CONSTANT_CONVERSION = YES;
161 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
162 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
163 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
164 | CLANG_WARN_EMPTY_BODY = YES;
165 | CLANG_WARN_ENUM_CONVERSION = YES;
166 | CLANG_WARN_INFINITE_RECURSION = YES;
167 | CLANG_WARN_INT_CONVERSION = YES;
168 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
169 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
170 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
171 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
172 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
173 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
174 | CLANG_WARN_STRICT_PROTOTYPES = YES;
175 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
176 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
177 | CLANG_WARN_UNREACHABLE_CODE = YES;
178 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
179 | COPY_PHASE_STRIP = NO;
180 | DEBUG_INFORMATION_FORMAT = dwarf;
181 | ENABLE_STRICT_OBJC_MSGSEND = YES;
182 | ENABLE_TESTABILITY = YES;
183 | GCC_C_LANGUAGE_STANDARD = gnu11;
184 | GCC_DYNAMIC_NO_PIC = NO;
185 | GCC_NO_COMMON_BLOCKS = YES;
186 | GCC_OPTIMIZATION_LEVEL = 0;
187 | GCC_PREPROCESSOR_DEFINITIONS = (
188 | "DEBUG=1",
189 | "$(inherited)",
190 | );
191 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
192 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
193 | GCC_WARN_UNDECLARED_SELECTOR = YES;
194 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
195 | GCC_WARN_UNUSED_FUNCTION = YES;
196 | GCC_WARN_UNUSED_VARIABLE = YES;
197 | MACOSX_DEPLOYMENT_TARGET = 11.1;
198 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
199 | MTL_FAST_MATH = YES;
200 | ONLY_ACTIVE_ARCH = YES;
201 | SDKROOT = macosx;
202 | };
203 | name = Debug;
204 | };
205 | B6313D7125B4341C0035448B /* Release */ = {
206 | isa = XCBuildConfiguration;
207 | buildSettings = {
208 | ALWAYS_SEARCH_USER_PATHS = NO;
209 | CLANG_ANALYZER_NONNULL = YES;
210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
212 | CLANG_CXX_LIBRARY = "libc++";
213 | CLANG_ENABLE_MODULES = YES;
214 | CLANG_ENABLE_OBJC_ARC = YES;
215 | CLANG_ENABLE_OBJC_WEAK = YES;
216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
217 | CLANG_WARN_BOOL_CONVERSION = YES;
218 | CLANG_WARN_COMMA = YES;
219 | CLANG_WARN_CONSTANT_CONVERSION = YES;
220 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
222 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
223 | CLANG_WARN_EMPTY_BODY = YES;
224 | CLANG_WARN_ENUM_CONVERSION = YES;
225 | CLANG_WARN_INFINITE_RECURSION = YES;
226 | CLANG_WARN_INT_CONVERSION = YES;
227 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
228 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
231 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
232 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
233 | CLANG_WARN_STRICT_PROTOTYPES = YES;
234 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
235 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
236 | CLANG_WARN_UNREACHABLE_CODE = YES;
237 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
238 | COPY_PHASE_STRIP = NO;
239 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
240 | ENABLE_NS_ASSERTIONS = NO;
241 | ENABLE_STRICT_OBJC_MSGSEND = YES;
242 | GCC_C_LANGUAGE_STANDARD = gnu11;
243 | GCC_NO_COMMON_BLOCKS = YES;
244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
246 | GCC_WARN_UNDECLARED_SELECTOR = YES;
247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
248 | GCC_WARN_UNUSED_FUNCTION = YES;
249 | GCC_WARN_UNUSED_VARIABLE = YES;
250 | MACOSX_DEPLOYMENT_TARGET = 11.1;
251 | MTL_ENABLE_DEBUG_INFO = NO;
252 | MTL_FAST_MATH = YES;
253 | SDKROOT = macosx;
254 | };
255 | name = Release;
256 | };
257 | B6313D7325B4341C0035448B /* Debug */ = {
258 | isa = XCBuildConfiguration;
259 | buildSettings = {
260 | CODE_SIGN_STYLE = Manual;
261 | DEVELOPMENT_TEAM = "";
262 | PRODUCT_BUNDLE_IDENTIFIER = "com.andy.linked-order-analyze";
263 | PRODUCT_NAME = "$(TARGET_NAME)";
264 | PROVISIONING_PROFILE_SPECIFIER = "";
265 | };
266 | name = Debug;
267 | };
268 | B6313D7425B4341C0035448B /* Release */ = {
269 | isa = XCBuildConfiguration;
270 | buildSettings = {
271 | CODE_SIGN_STYLE = Manual;
272 | DEVELOPMENT_TEAM = "";
273 | PRODUCT_BUNDLE_IDENTIFIER = "com.andy.linked-order-analyze";
274 | PRODUCT_NAME = "$(TARGET_NAME)";
275 | PROVISIONING_PROFILE_SPECIFIER = "";
276 | };
277 | name = Release;
278 | };
279 | /* End XCBuildConfiguration section */
280 |
281 | /* Begin XCConfigurationList section */
282 | B6313D6625B4341C0035448B /* Build configuration list for PBXProject "Linked_Order_Analyze" */ = {
283 | isa = XCConfigurationList;
284 | buildConfigurations = (
285 | B6313D7025B4341C0035448B /* Debug */,
286 | B6313D7125B4341C0035448B /* Release */,
287 | );
288 | defaultConfigurationIsVisible = 0;
289 | defaultConfigurationName = Release;
290 | };
291 | B6313D7225B4341C0035448B /* Build configuration list for PBXNativeTarget "Linked_Order_Analyze" */ = {
292 | isa = XCConfigurationList;
293 | buildConfigurations = (
294 | B6313D7325B4341C0035448B /* Debug */,
295 | B6313D7425B4341C0035448B /* Release */,
296 | );
297 | defaultConfigurationIsVisible = 0;
298 | defaultConfigurationName = Release;
299 | };
300 | /* End XCConfigurationList section */
301 | };
302 | rootObject = B6313D6325B4341C0035448B /* Project object */;
303 | }
304 |
--------------------------------------------------------------------------------