从 MachO 加载到对象创建!

MachO

MachO -- Mach Object, 是一种用于可执行文件/目标文件(.o)/动态库的文件格式. 作为a.out格式的替代.
  • 常见 MachO 格式的文件:
    目标文件.o / 库文件 / .a / .dylib / framework / .nib / 可执行文件 / dyld / .dysm(符号表) 等.
    • clang -c test.c 编译单个.c 文件得到.o 文件
    • file test.o ==> test.o: Mach-O 64-bit object x86_64; 可见 .o 也是一种 object 格式的 MachO 文件.
    • clang test.o 可以得到一个 a.out 文件, file a.out ==> a.out: Mach-O 64-bit executable x86_64 可见 a.out 是 executable 格式的MachO 文件.
    • file /usr/lib/libate.dylib ==> /usr/lib/libate.dylib: Mach-O universal binary with 3 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [x86_64h] .dylib 也是 universal binary 格式的 MachO 文件, 其中包含一个 dynamically linked shared library 文件.
otool 可以根据相关指令打印相关文件的信息
  • 相关指令集
otool [-arch arch_type]
      [-fahlLDtdorSTMRIHGvVcXmqQjCP]
      [-mcpu=arg]
      [--version] 
      

  • eg: otool -h MachO -- 查看 Mach header 信息.
Mach header
 magic      cputype cpusubtype caps filetype ncmds sizeofcmds flags
 0xfeedface 12      9          0x00 2        22    2512       0x00200085
MachO 文件结构
通过 MachOView 工具可以查看 MachO 结构. 
  • MachO Header :
    • 可以快速确定该文件的一些基本信息.
(loader.h)
struct mach_header_64 {
     uint32_t  magic; /* mach magic number identifier - 标记32-bit / 64-bit */
     cpu_type_t cputype; /* cpu specifier -CPU_TYPE_ARM 标记CPU类型(machine.h) */
     cpu_subtype_t cpusubtype; /* machine specifier - 具体类型(machine.h) */
     uint32_t  filetype;   /* type of file - MH_EXECUTE - 文件类型 */
     uint32_t  ncmds;      /* number of load commands - 指令数量 */
     uint32_t  sizeofcmds; /* the size of all the load commands - 指令总大小 */
     uint32_t  flags; /*flags -标记该文件支持的功能 MH_NOUNDEFS/MH_DYLDLINK... */
     uint32_t  reserved;   /* reserved */
};
  • Load-Commonds 包含指令集
    • 所有的命令的总大小由 mach_header -> sizeofcmds 给出.
    • 所有的指令前两个字段必须是 cmd 和 cmdsize, cmd 指令类型, 每个指令类型结构都不一样; cmdsize 指令大小, 包含指定命令大小加上后面部分命令大小(i.e. section structures, strings, etc.). 可以将cmdsize添加到当前命令的偏移量中执行下一条命令.
  struct load_command {
         uint32_t cmd;        /* type of load command */
         uint32_t cmdsize;    /* total size of command in bytes */
  }
  • segment_command_64
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 */
};
  • SEG_PAGEZERO 初始位置

    • LC_SEG_TEXT 包含 __cstring, methname, classname, methtype等.
    • LC_SEG_DATA 包含 classlist, protolist, imageinfo, const, selrefs(sel 指针), classrefs, superrefs, ivar, _objc_data(类对象数据结构,包含 isa, super class等), __data(协议类对象数据结构),
    • LC_SEG_LINKEDIT Dynamic Load Info, Function Starts(包含所有调用的方法), Symbol Table(符号表), Dynamic Symbol Table(动态符号表), String Table, Code Signature(签名信息)等.
    • LC_MAIN (标记 main 函数入口);
    • LC_LOAD_DYLIB (载入动态库);
    • LC_SYMTAB (符号表)
    • LC_LOAD_DYLINKER load a dynamic linker 加载动态链接器
    • ... 更多指令(loader.h).
  • Data 包含 Segement 数据

    • Section
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 */
};
   
  • ... 更多信息(loader.h)

DYLD

  • 系统内核经过一系列调用到 libsystem_init 后, 会调用 _dyld_initializer 初始化 dyld ==> dyldbootstrap::start
uintptr_t start(const struct macho_header* appsMachHeader, 
                                       int argc, 
                               const char* argv[], 
                                  intptr_t slide, 
                const struct macho_header* dyldsMachHeader,
                                uintptr_t* startGlue)

该函数会找到 ASLR, rebaseDyld, 然后会进入到 dyld::_main (dyld入口)中:

_main(const macho_header* mainExecutableMH, 
                uintptr_t mainExecutableSlide, 
                      int argc, 
              const char* argv[], 
              const char* envp[], 
              const char* apple[], 
               uintptr_t* startGlue)
  • 在 _main 函数中会先 配置上下文环境 setContext() ==> ImageLoader::LinkContext gLinkContext
  • 还有一些限制 configureProcessRestrictions()同样是 gLinkContext配置
  • 读取环境变量sEnv.xxxx(scheme 中的 arguments-environment Variables) eg:(DYLD_PRINT_OPTS / DYLD_PRINT_ENV)...
  • 然后加载 共享缓存库: mapSharedCache() ==> loadDyldCache()
  • 继而通过 macho_header, ASLR, Path 将 machO 实例化(在lldb调试中 => imagelist(0) 即源文件 MachO), 并配置到 gLinkContext 中, 另外, 也会根据 DYLD_INSERT_LIBRARIES 环境变量加载一些动态库 :
   if  (sEnv.DYLD_INSERT_LIBRARIES != NULL) {
       for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; 
                              *lib != NULL; 
                               ++lib) 
           loadInsertedDylib(*lib);
   }
  • 然后就会 链接主程序 => 再链接动态库
void ImageLoader::link(const LinkContext& context, 
                                    bool forceLazysBound, 
                                    bool preflightOnly, 
                                    bool neverUnload, 
                       const RPathChain& loaderRPaths, 
                             const char* imagePath)
  • 链接完成后会 初始化主程序 :
void initializeMainExecutable() {
   ...
   // run initializers for main executable and everything it brings up 
   // 循环
   sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); 
           
   // 主程序        
   sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]); 
   ...
}

notifySingle

  • 这里 (runInitializers) 会调用 processInitializers() ==> recursiveInitialization ==> context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
    context.notifySingle(dyld_image_state_initialized, this, NULL);
static void notifySingle(dyld_image_states state, 
                            const ImageLoader* image, 
           ImageLoader::InitializerTimingList* timingInfo)
  • 这里也会调用到 context.notifyBatch(dyld_image_state_initialized, false);

    • init 回调!!!

      notifySingle 中会通过 dyld_image_states state (dyld_image_state_dependents_initialized / dyld_image_state_initialized)判断调用到 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()) ==> (load_images 回调) ;

main()函数入口 (LC_MAIN)
* 初始化主程序完成后会通知 dyld 程序将进入main(), 接下来就会寻找 main()函数入口, 主要通过 MachO 中 load_command->cmd == LC_MAIN 查找 : result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN()

  • 注册回调在 libdispatch_init(), 由 libsystem_init 调起,
    libdispatch_init() 会调用 os_object_init(), 进而调用 objc_init(),
    objc_init 会调起 dyld::
    _dyld_objc_notify_register
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
                                  _dyld_objc_notify_init init,
                              _dyld_objc_notify_unmapped unmapped)
{
       dyld::registerObjCNotifiers(mapped, init, unmapped);
}

函数并注册回调, 三个回调分别会在三种情况下分别调用 mapped(dyld_image_state_bound);
init(dyld_image_state_dependents_initialized/dyld_image_state_initialized);
unmapped(removeImage).
其中:

registerObjCNotifiers(_dyld_objc_notify_mapped, 
                        _dyld_objc_notify_init, 
                     _dyld_objc_notify_unmapped);

调用

static void notifyBatchPartial(dyld_image_states state, 
                                         bool orLater, 
              dyld_image_state_change_handler onlyHandler, 
                                         bool preflightOnly, 
                                         bool onlyObjCMappedNotification)

如果 image->getState() 是 dyld_image_state_bound 则会回调 mapped, dyld_image_state_initialized 则会回调 init, 另外 notifySingle / notifySingleFromCache 同样会调用 init 回调, unmapped 在 removeImage 时才会回调.

objc

objc_init

从 MachO 加载到对象创建!_第1张图片
objc_init callback.jpg

mapped回调:

主要读取已经加载过的 image

void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
  • 映射 image 时回调, 该函数根据 count(mach_header 数量), paths(路径), mhdrs(mach_header 数组)
extern "C" void map_images(unsigned count, 
                 const char * const paths[],
   const struct mach_header * const mhdrs[]);

进行一系列调用后会调用如下函数进行读取 image

extern void _read_images(header_info **hList, 
                            uint32_t hCount, 
                                 int totalClasses, 
                                 int unoptimizedTotalClass);

在读取 image 时, 该函数会初始化 taggedPointer ==>initializeTaggedPointerObfuscator();, 以及 gdb_objc_realized_classes 表用于存储 Classes, allocatedClasses 用于存储类对象和元类对象, 然后实现 classes, protocol, category 等. 期间环境变量, log等一些设置可以在 objc-env.h 中按需查找.
_read_images() 主要做如下操作 :

  • tagged pointer 初始化 -- initializeTaggedPointerObfuscator();
  • 查找如下信息均会存入 hash 表中, 方便后续查找.
  • 查找 Classes 包括未处理的未来类(懒加载类) / 类引入处理 --> NXMapTable *gdb_objc_realized_classes, 如果是重映射类则会存入 NXMapTable *remapped_class_map;
    • @selector 方法引用 / 调用 objc_msgSend --> NXMapTable *namedSelectors;
    • 查找协议 / 协议引入处理 --> NXMapTable *protocol_map;
    • 实现非懒加载类 / 实现未处理的未来类 / 分类查找并实现 --> 同Classes查找;
    • 类的实现 :
    • static Class realizeClass(Class cls):
      • 初始化 rw :
        • rw->ro = ro;
        • rw->flags;
        • rw->version;
      • 初始化 cls :
        • cls->setData(rw);
        • cls->chooseClassArrayIndex();
        • cls->superclass = supercls;
        • cls->initClassIsa(metacls);
        • cls->setInstanceSize(ro->instanceSize);
        • cls->setHasCxxCtor();
        • 将该类和父类相关联 :
        // Connect this class to its superclass's subclass lists
            if (supercls) {
                addSubclass(supercls, cls);
            } else {
                addRootClass(cls);
            }
  • methodizeClass(cls) // 方法实现
  • methodizeClass 方法依次对方法列表 ro->baseMethods(),属性列表ro->baseProperties, 协议列表ro->baseProtocols, 分类unattachedCategoriesForClass() 进行 attachLists操作
  • attachLists 通过重新开辟数组 array_t *array(), 然后通过内存偏移以及内存拷贝到 array() 的 lists 中.
// 向前预留一个位置.
/* 将 array()->lists 从起始位置移动 oldCount * sizeof(array()->lists[0]) 字节
并用 array()->lists + addedCount 指针指向该内存. */
memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
/* 将 addlists 起始位置拷贝 addedCount * sizeof(array()->lists[0]) 字节
 到 array()->lists 所指的起始位置 */
memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

init回调:

  • init (load_images)回调中主要是调用class 的 +load 方法.
load_images(const char *path __unused, const struct mach_header *mh);

void prepare_load_methods(const headerType *mhdr) {
    // 循环添加类的 load 方法到 loadable_classes 结构体表中;
    schedule_class_load(remapClass(classlist[i]));
    
    // 循环添加分类 load 方法到loadable_category 结构体表中.
    add_category_to_loadable_list(cat); 
}

中, 根据 cls->data()->flags & RW_LOADED 判断, 遍历查找类的 load 方法 cls->getLoadMethod(), 并添加到loadable_classes中, 也会遍历 category 类并实现, 然后添加该分类的 +load 方法.

+ Load 方法调用!!!
prepare_load_methods 之后便会调用 void call_load_methods(void)
调用 call_class_loads() 和 call_category_loads() 分别调用类和分类的+load 方法.

void call_load_methods(void) {
    void *pool = objc_autoreleasePoolPush(); // 全部添加到 autoreleasePool中;
    
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // 循环取出 loadable_classes 结构体表中的结构体, 
            // 执行其中的 method --> (*load_method)(cls, SEL_load);
            call_class_loads(); 
        }

        // 2. Call category +loads ONCE
        // 循环取出 loadable_categories 结构体表中的结构体, 
        // 执行其中的 method --> (*load_method)(cls, SEL_load);
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
}

unmap_image 回调:

  • 主要是将 image 卸载:
    经过一系列调用后, 会进入以下函数:
 void _unload_image(header_info *hi)

该函数会先把未附加的 category 和等待 +load 的分类卸载, 然后通过对比 classlist =_getObjc2ClassList(hi, &count); 先将 isa, supercls, rootcls 以及 Metacls 分离,再卸载 classes;

alloc

首先[[NSObject alloc] init];
类的初始化前面已经介绍过, 进入 main 函数后, 在对象创建的时候, alloc 做了啥? init 又做了啥?
通过源码 objc :
alloc ==> _objc_rootAlloc(self) ==> callAlloc() ==== [cls allocWithZone:nil] ==> class_createInstance(cls, 0) ==> _class_createInstanceFromZone :

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil) {
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

首先断言该 cls 有没有实现,
然后获取该 cls 的实例大小(cls->instanceSize(extraBytes)), 然后通过calloc 开辟一片内存作为该对象的实例 :

void * calloc(size_t num_items, size_t size)
{
    void *retval;
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}

malloc_zone_calloc 会调用 ptr = zone->calloc(zone, num_items, size) 属性函数, 通过断点进入 ==> default_zone_calloc, 初始化 zone 后会调用
zone->calloc(): (lldb -> po)
(.dylib`nano_calloc at nano_malloc.c:880)
进入 nano_calloc ==> 发现该函数内定义了一个变量 p: 且在 size <= NANO_MAX_SIZE 的情况有返回, 就此判断该函内会返回一个真正开辟的空间对象.
void *p = _nano_malloc_check_clear(nanozone, size, 0);
进入后发现该函数内部对 instanceSize 作了处理, 通过 segregated_size_to_fit 函数对 size 作 (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM << SHIFT_NANO_QUANTUM 处理, 即 : size(转十六) + 15 后 抹掉末尾4位. 由此可知 : 对象 size 为16位对齐.
最终返回 一个 ptr 指针指向开辟的内存, 该指针即为 alloc 出来的对象实例!
而后便会初始化该对象的 isa ==> obj->initIsa(cls);

初始化 isa :

// nonpointer标记 isa 类型(是否是指针), hasCxxDtor 是否有 c++ 或 ObjC 析构器.
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.magic 标记该当前对象是 '真实对象' 还是 '没有初始化的空间', 即 magic
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        // isa.nonpointer 标记该对象 isa 是否是指针, 即 indexed
        newisa.bits = ISA_MAGIC_VALUE; 
        
        newisa.has_cxx_dtor = hasCxxDtor;
        
        // 存储 cls 信息, 简单位移, 移动 indexed, has_assoc, hasCxxDtor 三位, 
        // 即为 shiftcls 的信息, shiftcls 保存了 cls-shiftcls 的首地址.
        // 如下图 (移动了低位的三位).
        ps:有些资料里面说是内存对齐,不知道是不是我理解错误,还望了解的人不吝赐教,或者共同探讨.
        newisa.shiftcls = (uintptr_t)cls >> 3;
        
        isa = newisa;
    }
}
从 MachO 加载到对象创建!_第2张图片
isa_t.jpg

isa_t :

#define ISA_MASK        0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK  0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL

union isa_t { 
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t indexed           : 1; // nonpointer
        uintptr_t has_assoc         : 1; 
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44;
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
    };
};

init

[object init];
init ==> _objc_rootInit ==> return obj;
工厂设计模式.
子类重写, 初始化必要属性等一些操作.

你可能感兴趣的:(从 MachO 加载到对象创建!)