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::LinkContextgLinkContext
- 还有一些限制
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
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();
- 将该类和父类相关联 :
- 初始化 rw :
- @selector 方法引用 / 调用 objc_msgSend -->
// 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;
}
}
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;
工厂设计模式.
子类重写, 初始化必要属性等一些操作.