iOS开发之runtime(12):关于__objc_init_
本系列博客是本人的源码阅读笔记,如果有iOS开发者在看runtime的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
背景
上一篇文章中我们说道,static_init
方法使用到了区__objc_init_func
区的数据,那么
- 如何设置、获取数据
- 区的作用是什么
- 如何查看一个可执行文件的区
-
__objc_init_func
是怎样的一个区
本文将给出详细分析。
分析
接着上一篇文章的Mach-O的文件构成的概念,我们继续深入讲解Mach-O的构成,首先还是那张图:
mach-o
与上一篇文章中的图不一样的地方在于,后面给了一个实例,那这个示例是怎么样显示出来的呢,这里不得不提到一个优秀的查看Mach-O文件内部构成的软件:MachView
MachView
MachOView工具可Mac平台中可查看MachO文件格式信息,IOS系统中可执行程序属于Mach-O文件格式,有必要介绍如何利用工具快速查看Mach-O文件格式。MachOView工具属于免费开源项目。
git 地址:
https://git.code.sf.net/p/machoview/code
具体的使用流程这里不赘述了,文章末尾给出了几个参考地址,可以浏览一下。
Header
Header 中记录了 Mach-O 文件的属性信息,其数据结构定义在 loader.h中,分为32位以及64位:
32位:
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
struct mach_header {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
64位:
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
这里每个字段的意思如下:
字段介绍
因此简单总结一下就是 Headers 能帮助校验 Mach-O 合法性和定位文件的运行环境。
Load commands
Load commands 紧随在 Header 后,它包含了一系列的加载命令,目的是向操作系统描述如何处理 Mach-O 文件。
Load Commands 是跟在 Header 后面的加载命令区,所有 commands 的大小总和即为 Header->sizeofcmds 字段,共有 Header->ncmds 条加载命令。
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Segment
Mach-O 文件有多个段(Segment),每个段有不同的功能。然后每个段又分为很多小的 Section。 LC_SEGMENT 意味着这部分文件需要映射到进程的地址空间去。一般有以下段名:
__PAGEZERO: 空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对 NULL 指针的引用。
__TEXT: 包含了执行代码以及其他只读数据。该段数据可以 VM_PROT_READ(读)、VM_PROT_EXECUTE(执行),不能被修改。
__DATA: 程序数据,该段可写 VM_PROT_WRITE/READ/EXECUTE。
__LINKEDIT: 链接器使用的符号以及其他表。
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment 段的虚拟内存地址*/
uint32_t vmsize; /* memory size of this segment 段的虚拟内存大小*/
uint32_t fileoff; /* file offset of this segment 段在文件中的偏移量*/
uint32_t filesize; /* amount to map from the file 段在文件中的大小*/
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
其中 nsects 字段就是表明该段中有多少个 section。文件映射的起始位置是由 fileoff 给出,映射到地址空间的 vmaddr 处。
Section
Section 是具体有用的数据存放的地方。它的结构体跟随在 LC_SEGMENT 结构体之后,LC_SEGMENT 又在 Load Commands 中,但是 segment 的数据内容是跟在 Load Commands 之后的。它的结构体为:
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section 该节在内存中的起始位置*/
uint32_t size; /* size in bytes of this section 该节的大小*/
uint32_t offset; /* file offset of this section 该节的文件偏移*/
uint32_t align; /* section alignment (power of 2) 字节大小对齐*/
uint32_t reloff; /* file offset of relocation entries 重定位入口的文件偏移*/
uint32_t nreloc; /* number of relocation entries 需要重定位的入口数量*/
uint32_t flags; /* flags (section type and attributes) */
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
};
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
其中 flag 字段分为两个部分,一个是区域类型(section type),一个是区域属性(section attributes)。其中 type 是互斥的,即只能有一个类型,而 attributes 不是互斥的,可以有多个属性。如果段(segment)中的任何一个 section 拥有属性 S_ATTR_DEBUG,那么该段所有的 section 都必须拥有这个属性。具体的flag字段内容以及意义请参考 /usr/include/mach-o/loader.h。
段名为大写,节名为小写。各节的作用主要有:
__text: 主程序代码
__stub_helper: 用于动态链接的存根
__symbolstub1: 用于动态链接的存根
__objc_methname: Objective-C 的方法名
__objc_classname: Objective-C 的类名
__cstring: 硬编码的字符串
__lazy_symbol: 懒加载,延迟加载节,通过 dyld_stub_binder 辅助链接
_got: 存储引用符号的实际地址,类似于动态符号表
__nl_symbol_ptr: 非延迟加载节
__mod_init_func: 初始化的全局函数地址,在 main 之前被调用
__mod_term_func: 结束函数地址
__cfstring: Core Foundation 用到的字符串(OC字符串)
__objc_clsslist: Objective-C 的类列表
__objc_nlclslist: Objective-C 的 +load 函数列表,比 __mod_init_func 更早执行
__objc_const: Objective-C 的常量
__data: 初始化的可变的变量
__bss: 未初始化的静态变量
这里我们并没有看到__objc_init_func
,而是只有类似的__mod_init_func
区。那么__objc_init_func
是哪里来的呢?
全局搜索一下__objc_init_func
,可以发现在markgc.cpp中有如下代码:
template <typename P>
void dosect(uint8_t *start, macho_section<P> *sect)
{
if (debug) printf("section %.16s from segment %.16s\n",
sect->sectname(), sect->segname());
// Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call
// our init funcs because it is too late, and we don't want anyone to
// call our term funcs ever.
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_init_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_init_func");
if (debug) printf("disabled __mod_init_func section\n");
}
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_term_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_term_func");
if (debug) printf("disabled __mod_term_func section\n");
}
}
这段方法凭借我们读者的慧眼肯定能轻易看出,作用是将区__mod_init_func
替换成__objc_init_func
。
瞬间豁然开朗。
那么有会有朋友问,该markgc.cpp文件是何时被调用,并被写进runtime库的,这个又是一段故事了,请听笔者下回分解。
最后我们写个Demo 来设置区数据,并获取区数据:
#import <Foundation/Foundation.h>
#import <dlfcn.h>
#include <mach-o/loader.h>
#include <mach-o/getsect.h>
#ifndef __LP64__
#define mach_header mach_header
#else
#define mach_header mach_header_64
#endif
const struct mach_header *machHeader = NULL;
static NSString *configuration = @"";
//设置"__DATA,__customSection"的数据为kyson
char *kString __attribute__((section("__DATA,__customSection"))) = (char *)"kyson";
int main(int argc, const char * argv[]) {
@autoreleasepool {
//设置machheader信息
if (machHeader == NULL)
{
Dl_info info;
dladdr((__bridge const void *)(configuration), &info);
machHeader = (struct mach_header_64*)info.dli_fbase;
}
unsigned long byteCount = 0;
uintptr_t* data = (uintptr_t *) getsectiondata(machHeader, "__DATA", "__customSection", &byteCount);
NSUInteger counter = byteCount/sizeof(void*);
for(NSUInteger idx = 0; idx < counter; ++idx)
{
char *string = (char*)data[idx];
NSString *str = [NSString stringWithUTF8String:string];
NSLog(@"%@",str);
}
}
return 0;
}