IOS app launch optimization clang pile insertion

Time:2022-6-6

preface

IOS app startup optimization binary rearrangementWe described the process of the pre main phase of the app and the principle of binary rearrangement, and then we used this article to implement binary rearrangement.

1. Configure clang plug-in pile

We open clang’s official documentation# Clang 13 documentation
There is one hereTracing PCs, PC refers to the PC register. The CPU reads the code pointer, that is, the line of code that reads the virtual memory.
Therefore, tracing PCs tracks the code executed by the CPU.
How to use it is detailed in the official documents. Let’s configure it. Let’s add a tag first

-fsanitize-coverage=trace-pc-guard 

As shown in the figure

IOS app launch optimization clang pile insertion

1

Let’s compile it, as shown in the figure

IOS app launch optimization clang pile insertion

2

A link error is reported here. The symbol cannot be found because two callback functions need to be implemented

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

We compiled again, and the compilation was successful.
Here’s a lineprintf(“INIT: %p %p\n”, start, stop);Code. Let’s see what start and stop are. Let’s run them to see the effect, as shown in the figure

IOS app launch optimization clang pile insertion

3

Here are two addresses. Let’s analyze what these are.
startandstopBothuint32_tNamelyunsigned intType pointer, which indicates that the address printed above stores unsigned int type data. What exactly are these unsigned int type data? Let’s have a look, as shown in the figure

IOS app launch optimization clang pile insertion

4

therestartandstopRepresents the number of symbols. Let’s take a look at the last data, as shown in the figure

IOS app launch optimization clang pile insertion

5

Among them, 11000000 is the last data, and stop goes up 4 bytes to read the last data.
*for (uint32_t *x = start; x < stop; x++)
x = ++N;
Here is the number of symbols read from the start position to the stop position. Here are 11 symbols. Let’s verify

void test() {
    
}

We are in viewcontroller M join

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
}

Run again as follows

IOS app launch optimization clang pile insertion

6

Here it becomes 13. We just added two methods, which shows that our methods and functions are intercepted.
Let’s try block again, as follows

void (^block)(void) = ^(void) {
    
};

Let’s take another look, as shown in the figure

IOS app launch optimization clang pile insertion

7

This shows that our block has also been blocked.
The clang trace is global, and other files can be intercepted.

+ (void)load {
    
}

+ (void)initialize {
    
}

We add these two functions, and the debugging is shown in the figure below

IOS app launch optimization clang pile insertion

8

explainloadandinitializeMethods can be stopped.
__sanitizer_cov_trace_pc_guardLet’s debug this function and make a breakpoint, as shown in the figure below

IOS app launch optimization clang pile insertion

9

Run the project, click the screen and look at the stack, as shown in the figure

IOS app launch optimization clang pile insertion

10

We can seetouchesBeganThis method has been adjusted__sanitizer_cov_trace_pc_guardThis function, let’s change ittouchesBeganCode of, as follows

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Nslog (@ "screen clicked");
}

Run again, as shown in the figure

IOS app launch optimization clang pile insertion

11

After testing, it is found that,__sanitizer_cov_trace_pc_guardThis function was called before nslog. We added print to the block, test() function and found that it was called before nslog__sanitizer_cov_trace_pc_guardThis function indicates that it can intercept all symbols in the current project. System libraries and third-party libraries will not intercept.
What we rearrange is the binary implementation of the code. The system library and the third-party library are not in our
Generate mach-o files in the project.
The get and set methods generated by our custom attributes can be intercepted.

2 Principle Analysis of clang

__sanitizer_cov_trace_pc_guardThis function is the callback function of hook. Let’s analyze how it does it.
We make a breakpoint in this function, then run it, cross all breakpoints, make a breakpoint again, click the screen, and then we will analyze its assembly, as shown in the figure

IOS app launch optimization clang pile insertion

12

Let’s have a looktouchBegan, as shown in the figure

IOS app launch optimization clang pile insertion

13

It can be seen here that whentouchBeganWhen he was transferred, he entered immediately__sanitizer_cov_trace_pc_guardThis callback function,
We see the assemblybl 0x1000359e4 ; __sanitizer_cov_trace_pc_guard at ClangTrace.m:29, this is bl__sanitizer_cov_trace_pc_guardThis function indicates that as long as we add the flag of clang instrumentation, the compiler will add a sentence on the edge of the code implementation of all methods, functions and blocksbl 0x1000359e4 ; __sanitizer_cov_trace_pc_guardCode. This sentence is added before the code that implements the function. Similarly, there are such codes in functions and blocks.
This is equivalent to modifying the binary file. Let’s analyze how to modify it.
Insert a line of code in front of all methods. Only the compiler can do this. The compiler will insert this line of code when it reads our methods, functions and blocks.
We added the tag through other C flags, so we must insert the code during compilation.

3 get symbols

Our goal is to ultimately generate the order file, so we need to get the symbol name and order. How can we get it? Let’s analyze it.
Let’s open it firstvoid *PC = __builtin_return_address(0);This function,__builtin_return_addressThis function returns the address of the previous function, that is, the caller. This PC is the address of the previous function, that is, the address of the first line of code of the function. Line 0 is insertedbl 0x1000359e4 ; __sanitizer_cov_trace_pc_guard at ClangTrace.m:29code
We can get the name of the symbol through an address. The code is as follows

 Dl_info info;
 dladdr(PC, &info);

Import is required here#import <dlfcn.h>The header file and function information will be stored in the info structure. Let’s see the definition of this structure, as follows

/*
 * Structure filled in by dladdr().
 */
typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;
  • dli_fnameM file name
  • dli_fbaseAddress of m file
  • dli_snameName of function or method
  • dli_saddraddress

Let’s verify that the code is as follows

printf("fname:%s\nfbase:%p\nsname:%s\nsaddr:%p\n",info.dli_fname,info.dli_fbase,info.dli_sname, info.dli_saddr);

Operation, the effect is as follows

fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:+[ClangTrace load]
saddr:0x100029a30
INIT: 0x10002d788 0x10002d7e0
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:main
saddr:0x100029e5c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate setWindow:]
saddr:0x100029d7c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate application:didFinishLaunchingWithOptions:]
saddr:0x100029ad0
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[ViewController viewDidLoad]
saddr:0x1000297a4
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c

Here you get the symbol name and the call order.

4 saving symbols using atomic queues

We are__sanitizer_cov_trace_pc_guardjoin

NSLog(@"%@", [NSThread currentThread]);

Then ShunViewController.mFile addition code

+ (void)load {
    
}

+ (void)initialize {
    
}

void test() {
    Nslog (@ "test function execution");
    
}

void (^block)(void) = ^(void) {
    Nslog (@ "block function execution");
};

- (void)sleepT{
    sleep(3);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelectorInBackground:@selector(sleepT) withObject:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//Nslog (@ "screen clicked");
    test();
}

Run the project to see the effect, as shown below

2021-09-02 13:40:26.424 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
+[ViewController load]
2021-09-02 13:40:26.425 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
main
2021-09-02 13:40:27.311 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
+[ViewController initialize]
2021-09-02 13:40:27.312 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.318 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate setWindow:]
2021-09-02 13:40:27.324 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate application:didFinishLaunchingWithOptions:]
2021-09-02 13:40:27.324 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.325 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.330 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[ViewController viewDidLoad]
2021-09-02 13:40:27.336 ClangTrace[2817:871748] <NSThread: 0x12ed20eb0>{number = 2, name = (null)}
-[ViewController sleepT]
2021-09-02 13:40:27.346 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]

The thread information is printed here, where<NSThread: 0x12ed20eb0>{number = 2,name = (null)}This shows that it is also available in the child thread.
So__ sanitizer_ cov_ trace_ pc_ The callback guard is also multi-threaded. If our method is executed in the sub thread, the callback function is also executed in the sub thread. If data is stored here, there will be multi-threaded access, which will cause thread insecurity.
We use the thread safe queue osatomic for processing. The code is as follows

//Defining atomic queues
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

//Define the structure of the symbol
typedef struct {
    void * pc; //  Function address
    void * next; //  Next function node
}SymboNode;

We are revising__sanitizer_cov_trace_pc_guardThe code of this function is as follows

///Hook all callback functions
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    //Create structure
    SymboNode *node = malloc(sizeof(SymboNode));
    //Assign a value to the node first, and the next node will be empty for the time being
    *node = (SymboNode){PC, NULL};
    //The structure is put on the stack, the node is stored in the symbollist, and the next address is given to the next attribute of the node
    OSAtomicEnqueue(&symbolList, node, offsetof(SymboNode, next));
}

We aretouchesBeganAdd code

while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //Get symbol information
        Dl_info info;
        dladdr(node->pc, &info);
        printf("%s\n",info.dli_sname);

    }

Run to see the effect, as follows

[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]

I found the dead circle. Why?
__sanitizer_cov_trace_pc_guardThis function also intercepts our loop. How to solve it? We need to modify itOther C FlagsMarking of, as follows

-fsanitize-coverage=func,trace-pc-guard

Let’s run the project again to see the results, as follows

-[ViewController touchesBegan:withEvent:]
-[SceneDelegate sceneDidBecomeActive:]
-[SceneDelegate sceneWillEnterForeground:]
-[ViewController sleepT]
-[ViewController viewDidLoad]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate scene:willConnectToSession:options:]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate window]
+[ViewController initialize]
-[AppDelegate application:didFinishLaunchingWithOptions:]
main
+[ViewController load]

The result is normal, so we should only intercept methods.

5 method sequence adjustment and removal of repeated symbols

From the above running results, we can see that this order is reversed, and there are many repetitions here. We need to deal with it. The code is as follows

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //Define array
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //Get symbol information
        Dl_info info;
        dladdr(node->pc, &info);
        //To string
        NSString *name = @(info.dli_sname);
        //Distinguish the symbols of functions, block and OC methods. Functions and blocks are the same
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //Reverse traversal array
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    //Traversal to remove duplicate symbols
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    NSLog(@"%@",funArray);
}

6 generate order file

The last step is to write these intercepted symbols into the file. The code is as follows

//Define array
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //Get symbol information
        Dl_info info;
        dladdr(node->pc, &info);
        //To string
        NSString *name = @(info.dli_sname);
        //Distinguish the symbols of functions, block and OC methods. Functions and blocks are the same
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //Reverse traversal array
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    //Traversal to remove duplicate symbols
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    NSLog(@"%@",funArray);
    //Get rid of yourself
    [funArray removeObject:[NSString stringWithFormat:@"%s", __func__]];
    //Write order file
    //Become string
    NSString *funcStr = [funArray componentsJoinedByString:@"\n"];
    
    //Storage path
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingString:@"/clangTrace.order"];
    //Documents
    NSData *file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    
    //Create file
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    
    NSLog(@"%@", funcStr);

After running, we download the files on the real machine and find the clantrace Order file, as shown in the figure

IOS app launch optimization clang pile insertion

13

This saves it. We can use the order file, as shown in the figure

IOS app launch optimization clang pile insertion

14

Run. Let’s look at the contents of the map file, as shown in the figure

IOS app launch optimization clang pile insertion

15

The order is exactly the same as that of our order file.

7 swift symbol override

We create a swift file as follows

import UIKit

class SwiftTest: NSObject {
    
    @objc class public func swifttest () {
        print("swifttest......")
    }
}

And then inViewController.mAdd the load method in the file

+ (void)load {
    
    [SwiftTest swifttest];
}

Under operation, as shown in the figure

IOS app launch optimization clang pile insertion

16

This is a method that does not intercept swift. How do you solve it at this time?
We need to add the configuration, as shown in the figure

IOS app launch optimization clang pile insertion

17

Sanitize coverage=func and -sanitize=undefined parameters, running, as shown in the figure

IOS app launch optimization clang pile insertion

18

At this time, you can see that the swift method is also blocked. The swift symbol here is confused, which is automatically added by the compiler.

summary

In this article, we realized binary rearrangement through clang’s pile insertion, and solved many pits in this process. I also learned a lot of knowledge in this process. This article has many shortcomings, but I still hope it can bring you knowledge.

Complete code attached

//OC pile insertion mark
-fsanitize-coverage=func,trace-pc-guard
//Swift stake marker
sanitize-coverage=func
-sanitize=undefined

ClangTrace. H code is as follows

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ClangTrace : NSObject

///Generate order file
///@param filepath file path
void generateOrderFile(NSString *filePath);
@end

NS_ASSUME_NONNULL_END

ClangTrace. M code is as follows

#import "ClangTrace.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>

//Defining atomic queues
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

//Define the structure of the symbol
typedef struct {
    void * pc; //  Function address
    void * next; //  Next function node
}SymboNode;


@implementation ClangTrace

///Generate order file
///@param filepath file path
void generateOrderFile(NSString *filePath) {
    //Define array
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //Get symbol information
        Dl_info info;
        dladdr(node->pc, &info);
        //To string
        NSString *name = @(info.dli_sname);
        //Distinguish the symbols of functions, block and OC methods. Functions and blocks are the same
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //Reverse traversal array
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    //Traversal to remove duplicate symbols
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    //Get rid of yourself
    [funArray removeObject:[@"_" stringByAppendingFormat:@"%s", __func__]];
    //Write order file
    //Become string
    NSString *funcStr = [funArray componentsJoinedByString:@"\n"];
    
    if ([ClangTrace isBlankString:filePath]) {
        //Storage path
        filePath = [NSTemporaryDirectory() stringByAppendingString:@"/clangTrace.order"];
    }
    //Documents
    NSData *file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    
    //Create file
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    NSLog(@"\n%@", funcStr);
}


///Number of symbols in the project
///@param start start position
///@param stop end position
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
//    static uint64_t N;  // Counter for the guards.
//    if (start == stop || *start) return;  // Initialize only once.
//    printf("INIT: %p %p\n", start, stop);
//    for (uint32_t *x = start; x < stop; x++)
//       *x = ++N;  // Guards should start from 1.
}


///Hook all callback functions
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    //Create structure
    SymboNode *node = malloc(sizeof(SymboNode));
    //Assign a value to the node first, and the next node will be empty for the time being
    *node = (SymboNode){PC, NULL};
    //The structure is put on the stack, the node is stored in the symbollist, and the next address is given to the next attribute of the node
    OSAtomicEnqueue(&symbolList, node, offsetof(SymboNode, next));
}


///Judge whether the string is empty. The returned yes string is empty, and no is the opposite
///@param STR string
+ (BOOL)isBlankString:(NSString *)str {
    NSString *string = str;
    if (string == nil || string == NULL) {
        return YES;
    }
    if ([string isKindOfClass:[NSNull class]]) {
        return YES;
    }
    if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]==0) {
        return YES;
    }
    return NO;
}

@end