preface
This paper briefly introduces the concepts related to startup and some optimization schemes. In the future, this paper will introduce the optimization scheme of the pre-main stage, that is, binary rearrangement.
Before exploring binary rearrangement, expand on other aspects of the point concept.
A:Link Map File
1.1: what do you meanLink Map File
A Link Map File is a Link information File generated when Xcode generates an executable File. It describes the construction part of the executable File, including the distribution of code segments and data segments. Xcode does not generate an executable File by default and requires developers to manually set Target -> Build Setting -> Write Link Map File to YES:
You can also set the location of the Link Map File:
// The default location
$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt
/ / such as:/Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Intermediates.noind ex/LaunchTraceDemo.build/Debug-iphoneos/LaunchTraceDemo.build/LaunchTraceDemo-LinkMap-normal-arm64.txtCopy the code
Developers can also set the location of the file according to their own needs.
1.2: Link Map File
The composition of the
Double-click Link Map File to open it, you can find that it contains the following parts:
1.2.1: Path
# Path: /Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Products/Debug-ipho neos/LaunchTraceDemo.app/LaunchTraceDemoCopy the code
Path to the generated executable file.
1.2.2: Arch
# Arch: arm64
Copy the code
Path to the generated executable file.
1.2.3: Object files
# Object files:
[ 0] linker synthesized
[ 1] /Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Intermediates.noind ex/LaunchTraceDemo.build/Debug-iphoneos/LaunchTraceDemo.build/Objects-normal/arm64/ViewController.o [2] /Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Intermediates.noind ex/LaunchTraceDemo.build/Debug-iphoneos/LaunchTraceDemo.build/Objects-normal/arm64/AppDelegate.o [3] /Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Intermediates.noind ex/LaunchTraceDemo.build/Debug-iphoneos/LaunchTraceDemo.build/Objects-normal/arm64/main.o [4] /Users/baypac/Library/Developer/Xcode/DerivedData/LaunchTraceDemo-hbwolihehtukkzdwncleyihzvfrv/Build/Intermediates.noind ex/LaunchTraceDemo.build/Debug-iphoneos/LaunchTraceDemo.build/Objects-normal/arm64/SceneDelegate.o [5] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14. 5.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
[ 6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14. 5.sdk/usr/lib/libobjc.tbd
[ 7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14. 5.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
Copy the code
TBD lists all object files and dynamic libraries in the executable file. Each line is preceded by the file number.
1. :Sections
# Sections:
# Address Size Segment Section
0x100005F10 0x00000588 __TEXT __text
0x100006498 0x00000090 __TEXT __stubs
0x100006528 0x000000A8 __TEXT __stub_helper
0x1000065D0 0x000000BC __TEXT __objc_methlist
0x10000668C 0x00000D8A __TEXT __objc_methname
0x100007416 0x00000070 __TEXT __objc_classname
0x100007486 0x00000B0F __TEXT __objc_methtype
0x100007F95 0x00000016 __TEXT __cstring
0x100007FAC 0x00000054 __TEXT __unwind_info
0x100008000 0x00000008 __DATA_CONST __got
0x100008008 0x00000020 __DATA_CONST __cfstring
0x100008028 0x00000018 __DATA_CONST __objc_classlist
0x100008040 0x00000020 __DATA_CONST __objc_protolist
0x100008060 0x00000008 __DATA_CONST __objc_imageinfo
0x10000C000 0x00000060 __DATA __la_symbol_ptr
0x10000C060 0x000011D8 __DATA __objc_const
0x10000D238 0x00000078 __DATA __objc_selrefs
0x10000D2B0 0x00000010 __DATA __objc_classrefs
0x10000D2C0 0x00000008 __DATA __objc_superrefs
0x10000D2C8 0x00000004 __DATA __objc_ivar
0x10000D2D0 0x000000F0 __DATA __objc_data
0x10000D3C0 0x00000188 __DATA __data
Copy the code
Each Section contains Address, Size, Segment, and Section. Before I introduce you, here’s a quick look at the Mach-O file. The first part of the above Path is the Path of the executable file. Use iTerm to go to the folder, and then run the file command to check the type of the file:
file LaunchTraceDemo
Copy the code
The output is:
LaunchTraceDemo: Mach-O 64-bit executable arm64
Copy the code
You can see that the file is in Mach-O format, which is the iOS application execution file format. The virtual addresses in a Mach-O file are eventually mapped to physical addresses, which are divided into different Segment types: __TEXT, __DATA, __LINKEDIT, and so on. The meanings of each paragraph are as follows:
__TEXT
Contains the code being executed. This code is read-only and executable.__DATA
Contains data that will be changed, such as global variables, static variables, etc., which can be read or written, but not executed.__LINKEDIT
Contains loader metadata, such as function names and addresses, read-only.
The Segment is divided into sections that store different information. For example, __objc_methName is the method name and __objc_classList is the classlist.
1.2.5: Symbols
# Symbols:
# Address Size File Name
0x100005F10 0x00000048 [ 1] -[ViewController viewDidLoad]
0x100005F58 0x0000007C [ 2] -[AppDelegate application:didFinishLaunchingWithOptions:]
0x100005FD4 0x00000100 [ 2] -[AppDelegate application:configurationForConnectingSceneSession:options:]
0x1000060D4 0x00000074 [ 2] -[AppDelegate application:didDiscardSceneSessions:]
0x100006148 0x0000009C [ 3] _main
0x1000061E4 0x0000009C [ 4] -[SceneDelegate scene:willConnectToSession:options:]
0x100006280 0x0000004C [ 4] -[SceneDelegate sceneDidDisconnect:]
0x1000062CC 0x0000004C [ 4] -[SceneDelegate sceneDidBecomeActive:]
0x100006318 0x0000004C [ 4] -[SceneDelegate sceneWillResignActive:]
0x100006364 0x0000004C [ 4] -[SceneDelegate sceneWillEnterForeground:]
0x1000063B0 0x0000004C [ 4] -[SceneDelegate sceneDidEnterBackground:]
0x1000063FC 0x00000024 [ 4] -[SceneDelegate window]
0x100006420 0x0000003C [ 4] -[SceneDelegate setWindow:]
0x10000645C 0x0000003C [ 4] -[SceneDelegate .cxx_destruct]
0x100006498 0x0000000C [ 5] _NSStringFromClass
0x1000064A4 0x0000000C [ 7] _UIApplicationMain
0x1000064B0 0x0000000C [ 6] _objc_alloc
0x1000064BC 0x0000000C [ 6] _objc_autoreleasePoolPop
0x1000064C8 0x0000000C [ 6] _objc_autoreleasePoolPush
0x1000064D4 0x0000000C [ 6] _objc_autoreleaseReturnValue
0x1000064E0 0x0000000C [ 6] _objc_msgSend
0x1000064EC 0x0000000C [ 6] _objc_msgSendSuper2
0x1000064F8 0x0000000C [ 6] _objc_opt_class
0x100006504 0x0000000C [ 6] _objc_release
...
Copy the code
According to the starting address of Sections, Symbols can be divided into groups with the number of Sections, such as 0x100005F10 to 0x100006498, which is the __text code area. Symbols contains information such as:
Address
: Start addressSize
: Memory size, expressed in hexadecimal notation.File
: theSymbol
The number of the file you’re in, which isObject files
Part of the bracket number, for example-[ViewController viewDidLoad]
The corresponding file number is1
, according to theObject files
It can be seen that the owning file is:ViewController.o
.Name
Is that theSybmol
The name of the.
1.2.6: Dead Stripped Symbols
# Dead Stripped Symbols:
# Size File Name
<<dead>> 0x00000005 [ 2] literal string: hash
<<dead>> 0x0000000B [ 2] literal string: superclass
<<dead>> 0x0000000C [ 2] literal string: description
<<dead>> 0x00000011 [ 2] literal string: debugDescription
<<dead>> 0x00000007 [ 2] literal string: window
<<dead>> 0x00000009 [ 4] literal string: NSObject
...
Copy the code
Symbols considered useless by the linker are not credited when linking.
This is a brief introduction to Link Map files.
Two: binary rearrangement principle
After the introduction to virtual memory, you can see that when a Page of virtual memory is accessed and there is no mapping between the Page and physical memory, a Page Fault occurs, blocking the process. At this point, you need to load the data into physical memory, establish a mapping relationship, and then continue access. Although the process block caused by Page Fault is negligible at the millisecond level, in cold startup, a large number of classes, categories, and third-party libraries need to be loaded, resulting in a large number of Page missing exceptions, which is bound to affect the startup speed. Below, re-sign wechat to check the Page Fault times when wechat is started (re-signature will not be described here).
- Self-built project using script to re-sign wechat after installation while holding down
Command + i
Open,Instruments
, double-click to openSystem Trace
.
- Click on the
Record
(Cold boot requires restarting the phone to clear the cached data in physical memory.) After the first screen is displayed, clickStop
And then search according to the flow in the figureMain Thread
View virtual memoryPage Fault
The number of times.
As can be seen from the figure, the number of Page faults is as high as 4000+ times during this startup (and it is not cold startup, the number of Page faults will be higher during cold startup), which takes 636.31ms and greatly affects the startup speed.
Next, let’s look at the Symbols information in the Link Map File.
- Modify the
Build Setting -> Write Link Map File
forYES
.
Command + B
Compile the project and find the corresponding.app
The application package,Show in Finder
And then find it as shown in the figurelink map
File.
- Open the
link map
File, you can find symbols (OC
Method,C/C++
Functions, etc.) by class fileBuild Phases -> Compile Sources
The order in which the file is compiled and the order in which the file is written.
- will
AppDelegate.m
The compile order of the file is moved to first, and two methods are added to verify this.
Command + B
Compile the project again and viewlink map
File, you can see that after modification, the symbol order also changes.
From the case of Page Fault and symbol order above, can we make a guess: wechat needs to load 4000+ pages of memory data when it starts, but not all the data of each Page is necessary when it starts.
The root cause of too many Page faults during startup is that the symbols (OC methods, C/C++ functions, etc.) that need to be called during startup are in different pages. Therefore, our optimization idea is to set the symbols that need to be called at startup to the front, to form a number of pages required for startup. This avoids the problem of a large number of Page faults caused by the scattered symbols that need to be called during startup, reducing the number of Page faults and improving startup speed. This is the core principle of binary rearrangement.
Three: binary rearrangement practice
We can generate a. Order file and configure the path to Build Setting -> Order Files, and write the required symbols into the file in order. During compilation, the compiler will rearrange the binary according to the order of the symbols in the file. In order to achieve the effect of optimization. So, the essence of binary rearrangement is to rearrange the symbols that start loading.
Optimization scheme is now, if the project is lesser, can according to want to start the process will be added to the symbol sequence. The order file (small project is not recommended for binary rearrangement, could not start optimization effect), but if the project is bigger, startup involves much more special, now how do we get all the symbols of startup call? Here are a few ideas:
-
OC: The essence of the OC method is to send a message. Objc_msgSend is called at the bottom level. However, the objc_msgSend parameter is variable and needs to be obtained by assembly, which requires high requirements for developers.
-
Static scanning: Scanning for symbols and symbol data stored in certain sections and sections of Mach-O.
-
Clang piling: 100% symbol coverage can be achieved, that is, symbols related to C/C++, Swift, OC and block can be completely obtained.
3.1: Clang
Insert the pile
LLVM comes with a simple code coverage test built in. It inserts calls to user-defined functions at the function level, base block level, and edge level. Santizer coverage is needed for our batch hook here.
The official documentation for clang peg coverage is as follows: The clang code coverage tool documentation provides a detailed overview, as well as a brief Example.
-
[Step 1: Configure] Enable Santizer Coverage.
OC
The project needs to beBuild Setting -> Other C Flags
add-fsanitize-coverage=func,trace-pc-guard
.
Swift
Projects also require additional inBuild Setting -> Other Swift Flags
add-sanitize-coverage=func
and-sanitize=undefined
.
- If you want to fully cover all calls that include the tripartite library, link to
APP
The binaries in theSanitizerCoverage
. Also throughpodfile
To configure the parameters.
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['OTHER_CFLAGS'] = '-fsanitize-coverage=func,trace-pc-guard' config.build_settings['OTHER_SWIFT_FLAGS'] = '-sanitize-coverage=func -sanitize=undefined' end end end Copy the code
-
Create an OC file, XJOrderCallback, and override the two callback functions.
-
__sanitizer_cov_trace_pc_guard_init function
start
andstop
It’s a pointer, pointing tounsigned int
Type,4
Bytes corresponding to the executable filebegin
andend
(Example
thefor
Loop stores the number of symbols in),stop - 0x4
(stop
Gets the last piece of data, the number of bullets (excluding the tripartite library, where the tripartite library is not open)SanitizerCoverage
).
- Add two symbols (one function, one
block
), check againstop
.
-
__sanitizer_cov_trace_pc_guard function.
- As soon as it’s on
SanitizerCoverage
The compiler adds this function call to the “edge” of the code implementation of all symbols.
- This function gets all the symbolic addresses at startup, defines the nodes, and stores them in a linked list.
-
The OSQueueHead is used to create an atomic queue for read and write security.
-
Define the linked list node XJNode.
-
Nodes are enqueued through the OSAtomicEnqueue method, and the next node is accessed through the next pointer to the list.
-
- As soon as it’s on
-
// Atomic queue, whose purpose is to ensure write safety, thread safety
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
// Define a symbolic structure in the form of a linked list
typedef struct {
void *pc;
void *next;
}XJNode;
/* 'stop - 0x4' (' stop 'itself is the end flag) retrieves the last data, which is the bullet number */
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
printf("INIT: %p - %p\n", start, stop);
for (uint32_t*x = start; x < stop; x++) { *x = ++N; }}/* Can hook all symbols */
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (! *guard) return; // The load method is filtered out, so it needs to be commented out
/ / for the PC
/* -pc The current function returns the address of the previous call. -0 The current function address, i.e. the return address of the current function. -1 The current function caller's address, i.e. the return address of the previous function */
void *PC = __builtin_return_address(0);
// Create node and assign the value
XJNode *node = malloc(sizeof(XJNode));
*node = (XJNode){PC, NULL};
/ / team
// The symbol is accessed not by subscript, but by next pointer to the list, so we need to borrow offsetof (structure type, next address is next).
OSAtomicEnqueue(&symbolList, node, offsetof(XJNode, next));
}
Copy the code
-
[Step 3: Get all the symbols at startup and write them to the file]
-
The while loop fetches nodes from the queue, converts them to symbols, and prefixes non-OC symbols into arrays.
-
The array is reversed because the queue is stored in reverse order.
-
The array is de-duplicated and the current function’s symbol is removed.
-
Convert the array to a string and write it to the Xj.order file.
-
extern void getOrderFile(void(^completion)(NSString *orderFilePath)) {
__sync_synchronize();
// The symbol of the current function
NSString *functionExclude = [NSString stringWithFormat:@"_%s", __FUNCTION__];
// Create a symbol array
NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
// The while loop takes the symbol
while (YES) {
/ / out of the team
XJNode *node = OSAtomicDequeue(&symbolList, offsetof(XJNode, next));
if (node == NULL) break;
// get the address PC and convert it to Dl_info
Dl_info info;
dladdr(node->pc, &info);
// printf("%s \n", info.dli_sname);
if (info.dli_sname) {
// Check whether the OC method is used, if not, you need to underline the store, otherwise, directly store
NSString *name = @(info.dli_sname);
BOOL isObjc = [name hasPrefix:@"+ ["] || [name hasPrefix:@"-"];
NSString *symbolName = isObjc ? name : [@"_"stringByAppendingString:name]; [symbolNames addObject:symbolName]; }}if (symbolNames.count == 0) {
if (completion) {
completion(nil);
}
return;
}
// the queue is stored in reverse order.
NSEnumerator *emt = [symbolNames reverseObjectEnumerator];
/ / to heavy
NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString *name;
while (name = [emt nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
// Remove yourself
[funcs removeObject:functionExclude];
// Turn an array into a string
NSString *funcStr = [funcs componentsJoinedByString:@"\n"];
NSLog(@"Order:\n%@", funcStr);
// Write the string to the file
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"XJ.order"];
NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
if (completion) {
completion(success ? filePath : nil); }}Copy the code
- Note that the location of the call is up to you. Normally when the rendering of the main interface is complete.
-fsanitize-coverage=trace-pc-guard: / / Other C Flags set to -fsanitize-coverage=trace-pc-guard: / / Other C Flags set to -fsanitize-coverage=trace-pc-guard So be sure to use -fsanitize-coverage=func, trace-PC-guard.
The symbol list in xj.order is:
-
Step 5: Copy the file, put it in the specified location, and configure the path.
- In this paper, to generate
order
The file specifies the real machine’s sandbox directory, soXcode -> Devices and Simulators
Select the running device to download the sandbox file.
- Then go to the project directory to find the sandbox file, right-click and select Show package contents, and follow the path to find it
xj.order
File.
- will
xj.order
Copy the file to the project root directory.
- in
Build Settings -> Order File
In the configuration./xj.order
.
- The following is before and after configuration
Link Map File
Compare (the symbol order before the configuration and the symbol order after the configuration).
- In this paper, to generate
By comparing the Link Map files before and after, we can see that the executables are indeed binary rearranged according to our configuration.