在指定的segment和section中存入数据
之前我们已经了解过machO
的结构了,那么我们有没有办法去修改或者新增segment
和section
当中的数据呢,答案是可以的,使用__attribute__ section
将指定的数据存储到指定的segmemt
和section
中,也可以在通过在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")))
标记动态库符号是否可见,有以下取值:-
default
符号可见,可导出。 -
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,来实现加载启动模块的功能。