iOS之自定义Segment与Section

565 阅读3分钟

在指定的segment和section中存入数据

之前我们已经了解过machO的结构了,那么我们有没有办法去修改或者新增segmentsection当中的数据呢,答案是可以的,使用__attribute__ section将指定的数据存储到指定的segmemtsection中,也可以在通过在Build Settings中的Other Linker Flags设置链接参数,从而达到移动段,新增段,赋予权限等操作,有兴趣可以去看这篇文章iOS瘦身之__Text段移动

__attribute__的用法

__attribute__ 可以用来设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)

  • __attribute__((format())) 按照指定格式进行参数检查。

  • __attribute__((__always_inline__)) 强制内联。

  • __attribute__((deprecated("Use xxx: instead") 这个可能是我们见的比较多的,用来标记某个方法已经被废弃了,需要用其它的方法代替。

  • __attribute__((__unused__)) 标记函数或变量可能不会用到。


  • __attribute__((objc_direct)) 直接方法调用,感兴趣可以看看iOS之objc_direct

  • __attribute__((visibility("visibility_type"))) 标记动态库符号是否可见,有以下取值:

    1. default 符号可见,可导出。

    2. hidden 符号隐藏,不可导出,只能在本动态库内调用。


  • __attribute__((objc_designated_initializer)) 明确指定用于初始化的方法。一个优秀的设计,初始化接口可以有多个,但最终多个初始化初始化接口都会调用 designed initializer 方法。

  • __attribute__((unavailable))__attribute__((unavailable("Must use xxx: instead."))); 标记方法被禁用,不能直接调用,但这并不意味着该方法不能被调用,在 Objective-C 中使用 runtime 依然可以调用。

  • __attribute__((section("segment,section"))) 将一个指定的数据储存到我们需要的 segment 和 section 中。

  • __attribute__((constructor))attribute((constructor)) 标记的函数,会在 main 函数之前或动态库加载时执行。在 mach-o 中,被 attribute((constructor)) 标记的函数会在 _DATA 段的 __mod_init_func 区中。当多个被标记 attribute((constructor)) 的方法想要有顺序的执行,怎么办?attribute((constructor)) 是支持优先级的:_attribute((constructor(1)))

  • __attribute__((destructor))attribute((constructor)) 相反:被 attribute((destructor)) 标记的函数,会在 main 函数退出或动态库卸载时执行。在 mach-o 中此类函数会放在 _DATA 段的 __mod_term_func 区中

    // 定义一个结构体
    struct JYFuncTest{
        const char * className;
        const char * methodName;
    };
    ​
    // 创建一个SEGMENT_64为__JY ,然后__JY段里面创建一个__jySection的section64
    // 里面存放的是结构体 JYFuncTest
    struct JYFuncTest jy_section __attribute__((section ("__JY, __jySection"))) = {"JYThreadMonitor","startMonitor"};
    

    我们通过MachOView可以看到,Load Commands当中多了一个LC_SEGMENT_64(__JY),里面包含了我们创建的__jySection

我们如何读取这个段里面的信息呢? 还是通过那一套遍历header拿到SEGMENT,然后读取里面的数据,直接上代码

    
    if (machHeader == NULL) {
        Dl_info info;
        dladdr((__bridge const void *)(configuration), &info);
        machHeader = (struct mach_header_64*)info.dli_fbase;
    }
    
    unsigned long byteCount2 = 0;
    uintptr_t *data2 = (uintptr_t *)getsectiondata(machHeader, "__JY", "__jySection", &byteCount2);
    
    NSUInteger counter2 = byteCount2/sizeof(struct JYFuncTest);
    
    for (NSUInteger idx = 0; idx < counter2; ++idx) {
        
      struct JYFuncTest * test = (struct JYFuncTest*)&data2[idx];
      
      
      Class   testclass =   NSClassFromString([NSString stringWithUTF8String:test->symbol]);
        
      SEL sel = NSSelectorFromString([NSString stringWithUTF8String:test->machOName]);
        
      id (*func)(Class, SEL) = (id (*)(Class, SEL))objc_msgSend;
     
      func(testclass, sel);
​

具体使用场景

例如,你加载了很多静态库以及一些SDK,但是你项目的APM是在这之后运行的,那么这些库的加载就无法监听,但是设置section的时机是在main函数之前,那么就可以将自己项目当中的APM提到靠前的位置,在任何一个想要的独立模块当中,声明其模块名,并写入对应的section中,那么APP启动时,就可以通过访问指定的section,来实现加载启动模块的功能。

参考文献

iOS APP 启动优化(六):在指定的 segment 和 section 中存入数据

iOS开发之runtime(12):深入 Mach-O