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 FileThe 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:

  1. __TEXTContains the code being executed. This code is read-only and executable.
  2. __DATAContains data that will be changed, such as global variables, static variables, etc., which can be read or written, but not executed.
  3. __LINKEDITContains 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:

  1. Address: Start address
  2. Size: Memory size, expressed in hexadecimal notation.
  3. File: theSymbolThe number of the file you’re in, which isObject filesPart of the bracket number, for example-[ViewController viewDidLoad]The corresponding file number is1, according to theObject filesIt can be seen that the owning file is:ViewController.o.
  4. NameIs that theSybmolThe 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 downCommand + iOpen,Instruments, double-click to openSystem Trace.

  • Click on theRecord(Cold boot requires restarting the phone to clear the cached data in physical memory.) After the first screen is displayed, clickStopAnd then search according to the flow in the figureMain ThreadView virtual memoryPage FaultThe 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 theBuild Setting -> Write Link Map FileforYES.

  • Command + BCompile the project and find the corresponding.appThe application package,Show in FinderAnd then find it as shown in the figurelink mapFile.

  • Open thelink mapFile, you can find symbols (OCMethod,C/C++Functions, etc.) by class fileBuild Phases -> Compile SourcesThe order in which the file is compiled and the order in which the file is written.

  • willAppDelegate.mThe compile order of the file is moved to first, and two methods are added to verify this.

  • Command + BCompile the project again and viewlink mapFile, 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:

  1. 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.

  2. Static scanning: Scanning for symbols and symbol data stored in certain sections and sections of Mach-O.

  3. 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: ClangInsert 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.

    • OCThe project needs to beBuild Setting -> Other C Flagsadd-fsanitize-coverage=func,trace-pc-guard.

    • SwiftProjects also require additional inBuild Setting -> Other Swift Flagsadd-sanitize-coverage=funcand-sanitize=undefined.

    • If you want to fully cover all calls that include the tripartite library, link toAPPThe binaries in theSanitizerCoverage. Also throughpodfileTo 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

      • startandstopIt’s a pointer, pointing tounsigned intType,4Bytes corresponding to the executable filebeginandend(ExampletheforLoop stores the number of symbols in),stop - 0x4(stopGets 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, oneblock), check againstop.

    • __sanitizer_cov_trace_pc_guard function.

      • As soon as it’s onSanitizerCoverageThe 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.

// 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 generateorderThe file specifies the real machine’s sandbox directory, soXcode -> Devices and SimulatorsSelect 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 itxj.orderFile.

    • willxj.orderCopy the file to the project root directory.

    • inBuild Settings -> Order FileIn the configuration./xj.order.

    • The following is before and after configurationLink Map FileCompare (the symbol order before the configuration and the symbol order after the configuration).

By comparing the Link Map files before and after, we can see that the executables are indeed binary rearranged according to our configuration.