IMG_5617.JPG

dyld链接image后如何加载到内存中?

  • dyld -> images -> 内存 -> Person (方法、协议…)
  • images(macho) -> 地址 -> 表 -> 类 -> 初始化(rw - ro)

未命名文件 (4).jpg

objc_init分析

_objc_init 中注册回调_dyld_objc_notify_register之前,还有一些列初始化操作。

  • environ_init() 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助
  • tls_init() 关于线程key的绑定,比如:线程数据的析构函数
  • static_init() 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init()
  • runtime_init() runtime运行时环境初始化
  • exception_init() libobjc异常处理系统初始化
  • cache_t::init() 缓存条件初始化
  • _imp_implementationWithBlock_init() 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的
  • _dyld_objc_notify_register(&map_images, load_images, unmap_image)
    • _dyld_objc_notify_register — dyld 注册的地方
    • 仅供objc运行时使用
    • 注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
    • map_images: dyld将image镜像文件加载进内存时,会触发该函数
    • load_images:dyld初始化image会触发该函数
    • unmap_image:dyld将image移除时会触发该函数

      environ_init()

      建立CCPerson类
  1. 通过x/4gx 查看isa的内存地址
  2. 通过p/t 查看二进制地址,其末位是1,代表nonpointerIsa ```cpp CCPerson *p = [CCPerson alloc];

x/4gx p 0x108db8e90: 0x011d8001000083d1 0x0000000000000000 0x108db8ea0: 0x0000000108db8f70 0x0000000108db91b0

p/t 0x011d8001000083d1 (long) $1 = 0b0000000100011101100000000000000100000000000000001000001111010001

  1. - **环境变量的全部配置指令,在终端输入**`**export OBJC_HELP=1**`
  2. - 接着在`Scheme`中设置环境变量并勾选
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1626137861272-14bcb912-c473-483a-9212-95b54522b3b7.png#clientId=ua9d5af68-836e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1008&id=u91d2ead5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1008&originWidth=1866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=179781&status=done&style=none&taskId=u3d6c3dcf-6942-4e5f-93eb-d2604b16a70&title=&width=1866)
  4. - 重新运行指令,x/4gx p/t, 观察到其末位变为0,为非nonpointerIsa
  5. ```cpp
  6. x/4gx p
  7. 0x108c74670: 0x00000001000083d0 0x0000000000000000
  8. 0x108c74680: 0x65766153534e5b2d 0x6573206c656e6150
  9. p/t 0x00000001000083d0
  10. (long) $1 = 0b0000000000000000000000000000000100000000000000001000001111010000
  • 同理,在环境变量中勾选打印load_methods,可以将全部load方法打印出来

image.png
image.png

tls_init()

本地线程线程池的初始化和析构

  1. void tls_init(void)
  2. {
  3. #if SUPPORT_DIRECT_THREAD_KEYS
  4. pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
  5. #else
  6. _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
  7. #endif
  8. }

static_init()

全局静态C++函数调用

  • libc calls _objc_init() before dyld would call our static constructors
  • _objc_init() 调用先于dyld中的调用

    1. static void static_init()
    2. {
    3. size_t count;
    4. auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    5. for (size_t i = 0; i < count; i++) {
    6. inits[i]();
    7. }
    8. auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    9. for (size_t i = 0; i < count; i++) {
    10. UnsignedInitializer init(offsets[i]);
    11. init();
    12. }
    13. }

    runtime_init()

    runtime初始化两张表

  • allocatedClasses,已经被开辟过class的一张表

  • unattachedCategories,分类未链接表

    1. void runtime_init(void)
    2. {
    3. objc::unattachedCategories.init(32);
    4. objc::allocatedClasses.init();
    5. }
    6. void init(Ts &&... Args) {
    7. new (_storage) Type(std::forward<Ts>(Args)...);
    8. }

    exception_init()

  • 异常捕获,程序底层有runloop循环执行,在发生异常时会捕获exceptions

  • 如果上层对uncaught_handler进行了赋值,下了句柄,相对于底层对上层有了回调,因此可以在上层处理此异常,防止了崩溃

1631457452843.jpg

  1. void exception_init(void)
  2. {
  3. old_terminate = std::set_terminate(&_objc_terminate);
  4. }
  5. static void _objc_terminate(void)
  6. {
  7. if (PrintExceptions) {
  8. _objc_inform("EXCEPTIONS: terminating");
  9. }
  10. if (! __cxa_current_exception_type()) {
  11. // No current exception.
  12. (*old_terminate)();
  13. }
  14. else {
  15. // There is a current exception. Check if it's an objc exception.
  16. @try {
  17. __cxa_rethrow();
  18. } @catch (id e) {
  19. // It's an objc object. Call Foundation's handler, if any.
  20. (*uncaught_handler)((id)e);
  21. (*old_terminate)();
  22. } @catch (...) {
  23. // It's not an objc object. Continue to C++ terminate.
  24. (*old_terminate)();
  25. }
  26. }
  27. }

_dyld_objc_notify_register

  • &map_images,引用类型,指针传递,保证内外部变化同步,映射整个镜像文件,非常重要,一处发送错误,整个都会发生错乱
  • loadImages

未命名文件-2.jpg

map_images——重点

  • 除了lookUpImpOrForward,第二个重点函数
  • 将mach-o文件映射到内存中,map_images -> map_images_nolock -> _read_images ```cpp _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
  1. **_read_images主要做了哪些事情**
  2. 1. **条件控制进行一次的加载**
  3. 1. **修复预编译阶段的**[@selector(selector)](/selector)**混乱问题**
  4. 1. **错误混乱的类处理**
  5. 1. **修复重映射一些没有被镜像文件加载进来的类**
  6. 1. **修复一些消息**
  7. 1. **当我们的类里面有协议的时候readProtocol**
  8. 1. **修复没有被加载的协议**
  9. 1. **分类处理**
  10. 1. **类的加载处理**
  11. 1. **没有被处理的类,优化那些被侵犯的类**
  12. <a name="NjdfJ"></a>
  13. #### 条件控制进行一次的加载
  14. - initializeTaggedPointerObfuscator小对象数据类型,进行混淆
  15. - cacheimp & mask 出现过类似的混淆
  16. ```cpp
  17. static void initializeTaggedPointerObfuscator(void)
  18. {
  19. if (!DisableTaggedPointerObfuscation) {
  20. // Pull random data into the variable, then shift away all non-payload bits.
  21. arc4random_buf(&objc_debug_taggedpointer_obfuscator,
  22. sizeof(objc_debug_taggedpointer_obfuscator));
  23. objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
  24. #if OBJC_SPLIT_TAGGED_POINTERS
  25. // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
  26. objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
  27. // Shuffle the first seven entries of the tag permutator.
  28. int max = 7;
  29. for (int i = max - 1; i >= 0; i--) {
  30. int target = arc4random_uniform(i + 1);
  31. swap(objc_debug_tag60_permutations[i],
  32. objc_debug_tag60_permutations[target]);
  33. }
  34. #endif
  35. } else {
  36. // Set the obfuscator to zero for apps linked against older SDKs,
  37. // in case they're relying on the tagged pointer representation.
  38. objc_debug_taggedpointer_obfuscator = 0;
  39. }
  40. }

表的创建 — 容量表

  • namedClassesSize总容量 = totalClasses 4 / 3 , 例如 totalClasses = 8时,开辟空间 x = (totalClasses 4 / 3) * 3/4时进行扩容
  • NXCreateMapTable根据容量创建表-总表

    • gdb_objc_realized_classes总表(不在dyld的缓存中,无论是否被实现过)
    • 对比上述runtime创建allocatedClasses表,代表所有类被实现的表
      1. int namedClassesSize =
      2. (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
      3. gdb_objc_realized_classes =
      4. NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

      修复预编译阶段的selector混乱问题

  • 相对位置修复,mach-o文件和dyld中的sel名字相同,其地址可能不同,所以进行局部修复

  • 在llvm就加载了sel, sel = 名字 + 地址
  • image.png

    错误混乱的类处理

    resolvedFutureClasses处理未被加载的类,部分类被删除时混乱,还存在内存中

    1. resolvedFutureClasses = (Class *)
    2. realloc(resolvedFutureClasses,
    3. (resolvedFutureClassCount+1) * sizeof(Class));
    4. resolvedFutureClasses[resolvedFutureClassCount++] = newCls;

    核心重点readClass

  • readClass传入cls,所以这里我们可以打印观察读取的类

  • readClass对类做了处理,在mach-o中,class都是符号,得出结果则是NSStackBlock这种处理过的类 ```cpp Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

printf(“%s -CC: 要研究的: - %s\n”,func,cls->nonlazyMangledName()); ```

  • 这里是先加载系统类,再加载我们创建的类

image.png
image.png
将类加载到hashMap和表中,这里还未涉及class_rw、class_ro操作
1631460653805.jpg

  • addNamedClass(Class cls, constchar *name, Class replacing = nil) -> NXMapInsert(gdb_objc_realized_classes, name, cls),name和cls(地址)以key、value形式存储在hash表中
  • ro、rw相关处理都与realizeClassWithoutSwift(cls, ni)相关

类的加载原理(中)