iOS开发之runtime(12):关于__objc_init_func区

logo

本系列博客是本人的源码阅读笔记,如果有 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;
}

参考

Macho文件浏览器---MachOView
探秘 Mach-O 文件
解读 Mach-O 文件格式

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容