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

Let’s compile it, as shown in the figure

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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