A way to delay premain code

Posted by md7dani on Tue, 13 Aug 2019 04:55:59 +0200

Original Link: https://mp.weixin.qq.com/s/5jUYjBiXW05_l_n-sp9nog

A lot of premain code, uncontrollable, bombs are always on the line.To make the developer transition more "transparent", there are the following methods.

 

The source of the idea is still three articles that analyzed Facebook clients two years ago:

 

1-Explore facebook iOS client-section fbsessiongks

https://everettjf.github.io/2016/08/21/facebook-explore-section-fbsessiongks/
2-Explore facebook iOS Client-section FBInjectable

https://everettjf.github.io/2016/08/20/facebook-explore-section-fbinjectable/

3-Explore Facebook iOS Client-Section RODATA

https://everettjf.github.io/2016/08/19/facebook-explore-section-rodata/

 

There are three ways to get your code to execute before the main function:

  1. All +load methods

  2. All C++ static initializers

  3. All C/C++ attribute(constructor) functions

Problems Executing Before main Function

  1. Cannot Patch

  2. Cannot audit time-consuming

  3. Calling UIKit-related methods will cause some classes to initialize earlier

  4. Main thread execution, fully blocked execution

How to solve these problems

Whether there is a convenient way to port code that precedes the main function after the main function.

Idea Source

Facebook has a new section FBInjectable, so you can learn the meaning of this section by putting some data into a custom section during compilation and linking, and then getting the section's data in your program.

If the data is a string, we can get the class name from the string; if it is a function address, we can call it directly.

(Refer to the article https://everettjf.github.io/2016/08/20/facebook-explore-section-fbinjectable for the meaning of Facebook paragraph FBInjectable)

So how do I create an FBInjectable segment?

You can use the u attribute((used,section("segmentname,sectionname")) keyword to put a variable into a special section.

(attribute s refer to http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html)

For example:

char * kString1 __attribute((used,section("__DATA,FBInjectable"))) = "string 1";
char * kString2 __attribute((used,section("__DATA,FBInjectable"))) = "string 2";
char * kString3 __attribute((used,section("__DATA,FBInjectable"))) = "string 3";

After compilation, you can create a new FBInjectable section under the program's DATA segment s and use the addresses of kString1, kString2, and kString3 variables as FBInjectable section content.

How to apply

Simulate Facebook's code, which places the address of the function address (the value of varSampleObject) in the QWLoadable section.

typedef void (*QWLoadableFunctionTemplate)();
static void QWLoadableSampleFunction(){
    // Do something
}
static QWLoadableFunctionTemplate varSampleObject __attribute((used, section("__DATA,QWLoadable"))) = QWLoadableSampleFunction;

Then the main program obtains the contents of QWLoadable through getsectiondata at startup and calls them one by one.

Further Improvement

To mark the name of each function, you can have the function pass out from within as follows:

typedef int (*QWLoadableFunctionCallback)(const char *);
typedef void (*QWLoadableFunctionTemplate)(QWLoadableFunctionCallback);

static void QWLoadableSampleFunction(QWLoadableFunctionCallback QWLoadableCallback){
    if(0 != QWLoadableCallback("SampleObject")) return;

    // Do something
}

static QWLoadableFunctionTemplate varSampleObject __attribute((used, section("__DATA,QWLoadable"))) = QWLoadableSampleFunction;

This allows the function to tell the outside about its "identity" through QWLoadableCallback and gives the outside the ability to filter itself (without calling).

Invoked at startup

static int QWLoadableFunctionCallbackImpl(const char *name){
    // filter by name
    return 0;
}

static void QWLoadableRun(){
    CFTimeInterval loadStart = CFAbsoluteTimeGetCurrent();

    Dl_info info;
    int ret = dladdr(QWLoadableRun, &info);
    if(ret == 0){
        // fatal error
    }

#ifndef __LP64__
    const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
    unsigned long size = 0;
    uint32_t *memory = (uint32_t*)getsectiondata(mhp, QWLoadableSegmentName, QWLoadableSectionName, & size);
#else /* defined(__LP64__) */
    const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
    unsigned long size = 0;
    uint64_t *memory = (uint64_t*)getsectiondata(mhp, QWLoadableSegmentName, QWLoadableSectionName, & size);
#endif /* defined(__LP64__) */

    CFTimeInterval loadComplete = CFAbsoluteTimeGetCurrent();
    NSLog(@"QWLoadable:loadcost:%@ms",@(1000.0*(loadComplete-loadStart)));
    if(size == 0){
        NSLog(@"QWLoadable:empty");
        return;
    }

    for(int idx = 0; idx < size/sizeof(void*); ++idx){
        QWLoadableFunctionTemplate func = (QWLoadableFunctionTemplate)memory[idx];
        func(QWLoadableFunctionCallbackImpl);
    }

    NSLog(@"QWLoadable:callcost:%@ms",@(1000.0*(CFAbsoluteTimeGetCurrent()-loadComplete)));
}

Reform

Callers can migrate code originally in + load between two macros (QWLoadableFunctionBegin and QWLoadableFunctionEnd) as follows.

QWLoadableFunctionBegin(FooObject)
[BarObject userDefinedLoad];
// anything here
QWLoadableFunctionEnd(FooObject)

Dynamic Library

Dynamic libraries are individual, so segments of QWLoadable in dynamic libraries need to be handled separately.

performance

Porting code before the main function such as + load to the main function also adds a time-consuming read section.

Tested, 100 function addresses read, not more than 1ms on the device of the iPhone 5.This adds less than 1ms of time (which is also auditable), bringing auditable behavior for all start-up phases, and the most important Patch capabilities.

Reference Code

https://github.com/everettjf/Yolo/tree/master/LoadableMacro

summary

This is just the simplest example. Any address can be stored in a section, which is more flexible.

Topics: github iOS Attribute less