In-depth analysis of the relationship between dyld and ObjC in iOS

App startup and dylb loading

  • We know the loading process of dyld, that is, before the App starts to execute the main function, dylb mainly performs environment variable configuration, shared cache, initialization of the main program, inserting dynamic libraries, linking the main program, linking dynamic libraries, weak symbol binding, Perform a series of processing such as the initialization method and the search for the main program entry. For details, please refer to my previous blog: iOS in-depth analysis of the underlying principle of the App startup dyld loading process ;
  • During the execution of the main function, when dyld is loaded to start linking the main program, the recursiveInitialization function is called recursively.
  • The recursiveInitialization function is executed for the first time to initialize libsystem. The execution process is: recursiveInitialization -> doInitialization -> doModInitFunctions -> libSystemInitialized.
  • The initialization of libsystem will call libdispatch_init, and the init of libdispatch will call _os_object_init, which calls _objc_init.
  • The map_images, load_images, unmap_image function addresses are registered and saved in _objc_init, thus entering the loading process of our class.

_objc_init source code analysis

1. _objc_init bottom method
  • The objc4-781 official source code _objc_init method is as follows:
void _objc_init(void) {
    
    
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    // 什么时候调用? images 镜像文件
    // map_images()
    // load_images()
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • Source code interpretation:
    • environ_init(): Read the environment variables that affect the runtime. If necessary, you can also open the environment variables to help export OBJC_HRLP = 1;
    • tls_init(): Binding of thread keys, such as the destructor of thread data;
    • static_init(): Run the C++ static constructor, before dyld calls the static destructor, libc will call _objc_init();
    • runtime_init(): Initialization of runtime environment, mainly initialization of categories such as unattachedCategories and allocatedClasses;
    • exception_init(): Initialize the exception handling system of libobjc;
    • cache_init(): Initialization of cache conditions;
    • _imp_implementationWithBlock_init(): start the callback mechanism, usually this will not deal with anything, because all initialization is lazy, but for some processes, trampolines dylib will be loaded impatiently;
    • _dyld_objc_notify_register: The registration of dyld is only for objc runtime use. The handler is registered for use in mapping, unmapping and initializing objc image files. Dyld will use the array of image files containing objc_image_info to call back the mapped function;
Two, environ_init: initialization of environment variables
  • The source code of the environ_init method is as follows, the key code is the for loop:
	void environ_init(void) {
    
    
	    //...省略部分逻辑
	if (PrintHelp  ||  PrintOptions) {
    
    
	        //...省略部分逻辑
	        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
    
    
	            const option_t *opt = &Settings[i];            
	            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
	            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
	        }
	    }
	}
  • Separate the for loop, remove all conditions, put it into the project, and print the environment variables:
	objc[38640]: OBJC_DISABLE_TAG_OBFUSCATION is set
	objc[38640]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
	objc[38640]: OBJC_DISABLE_NONPOINTER_ISA is set
	objc[38640]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
	objc[38640]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
  • In the main function, put the following breakpoints:

Insert picture description here

  • When the environment variable OBJC_DISABLE_NONPOINTER_ISA is not set, LLDB prints the isa information of person:
	lldb) x/4gx person
	0x1010b5680: 0x001d800100008265 0x0000000000000000
	0x1010b5690: 0x0000000000000000 0x0000000000000000
	(lldb) p/t 0x001d800100008265
	(long) $1 = 0b0000000000011101100000000000000100000000000000001000001001100101
  • After setting the environment variable OBJC_DISABLE_NONPOINTER_ISA to YES (the configuration of the environment variable is configured in target – Edit Scheme – Run --Arguments – Environment Variables), then print the person’s isa information again:
	(lldb) x/4gx person
	0x100a09f20: 0x0000000100008260 0x0000000000000000
	0x100a09f30: 0x0000000000000000 0x0000000000000000
	(lldb) p/t 0x0000000100008260
	(long) $1 = 0b0000000000000000000000000000000100000000000000001000001001100000
  • It is not difficult to see from the above: the last bit of the binary $1 of the isa address has changed from 1 to 0. Through the analysis of the underlying structure of the isa, it can be seen that the last bit of the isa is the nonpointer bit, which indicates whether to enable pointer optimization for the isa pointer (0: pure isa Pointer; 1: more than just the address of the class object). The isa contains class information, object reference count, etc.
  • For the structural analysis of isa, please refer to my previous blog: iOS in-depth analysis of the underlying principle of the object isa ;
  • Configure the environment variable OBJC_PRINT_LOAD_METHODS of the print load method and set it to YES; then rewrite the +load function in the YDWPerson class:

Insert picture description here

  • Run the program and print again, the console log is as follows:
	...
	objc[39088]: LOAD: class 'NSApplication' scheduled for +load
	objc[39088]: LOAD: class 'NSBinder' scheduled for +load
	objc[39088]: LOAD: class 'NSColorSpaceColor' scheduled for +load
	objc[39088]: LOAD: class 'NSNextStepFrame' scheduled for +load
	objc[39088]: LOAD: category 'NSColor(NSUIKitSupport)' scheduled for +load
	objc[39088]: LOAD: +[NSApplication load]
	objc[39088]: LOAD: +[NSBinder load]
	...
	objc[39088]: LOAD: +[YDWPerson load]
	···
  • It can be seen from the above that: OBJC_PRINT_LOAD_METHOD S can monitor all +load methods to handle startup optimization;
3. tls_init: binding of thread key

Mainly the initialization and destruction of the local thread pool, the source code is as follows:

	void tls_init(void) {
    
    
	#if SUPPORT_DIRECT_THREAD_KEYS//本地线程池,用来进行处理
	    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);//初始init
	#else
	    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);//析构
	#endif
	}
Four, static_init: C++ static constructor at the operating system level

It is mainly to run the system-level C++ static constructor. Before dyld calls our static constructor, libc calls the _objc_init method, which is the system-level C++ constructor, which runs before the custom C++ constructor:

	static void static_init() {
    
    
	    size_t count;
	    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
	    for (size_t i = 0; i < count; i++) {
    
    
	        inits[i]();
	    }
	}
Five, runtime_init: runtime environment initialization

It is mainly the initialization at runtime, which is mainly divided into two parts: classification initialization and class table initialization:

	void runtime_init(void) {
    
    
	    objc::unattachedCategories.init(32);
	    objc::allocatedClasses.init(); //初始化 -- 开辟的类的表
	}
6. exception_init: Initialize the exception handling system of libobjc
  • The main purpose is to initialize libobjc's exception handling system, register exception handling callbacks, and monitor exception handling. The source code is as follows:
	void exception_init(void) {
    
    
	    old_terminate = std::set_terminate(&_objc_terminate);
	}
  • When a crash (crash refers to some commands that are not allowed by the system, and then some signals given by the system) occurs, it will come to the _objc_terminate method, and go to the uncaught_handler to throw an exception:
/***********************************************************************
 * _objc_terminate
 * Custom std::terminate handler.
 *  * The uncaught exception callback is implemented as a std::terminate handler. 
 * 1. Check if there's an active exception
 * 2. If so, check if it's an Objective-C exception
 * 3. If so, call our registered callback with the object.
 * 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    
    
    if (PrintExceptions) {
    
    
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
    
    
        // No current exception.
        (*old_terminate)();
    }
    else {
    
    
        // There is a current exception. Check if it's an objc exception.
        @try {
    
    
            __cxa_rethrow();
        } @catch (id e) {
    
    
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);//扔出异常
            (*old_terminate)();
        } @catch (...) {
    
    
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}
  • Search for uncaught_handler, and pass in a function to handle exceptions in the app layer to facilitate calling the function, and then return to the original app layer, as shown below, where fn is the passed function, that is, uncaught_handler is equal to fn:
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    
    
//    fn为设置的异常句柄 传入的函数,为外界给的
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn; //赋值
    return result;
}
Seven, cache_init: cache condition initialization
void cache_init() {
    
    
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
    
    
        count++;
    }
    // 为当前任务注册一组可重新启动的缓存
    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
8. _imp_implementationWithBlock_init: start the callback mechanism

This method is mainly to start the callback mechanism, usually this will not do anything, because all initialization is lazy, but for some processes, we can't wait to load libobjc-trampolines.dylib, the source code is as follows:

void
_imp_implementationWithBlock_init(void)
{
    
    
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    // 在某些进程中渴望加载libobjc-trampolines.dylib。一些程序(最著名的是嵌入式Chromium的较早版本使用的QtWebEngineProcess)启用了严格限制的沙箱配置文件,从而阻止了对该dylib的访问。如果有任何调用imp_implementationWithBlock的操作(如AppKit开始执行的操作),那么我们将在尝试加载它时崩溃。将其加载到此处可在启用沙箱配置文件之前对其进行设置并阻止它。
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
    
    
        Trampolines.Initialize();
    }
#endif
}
Nine, _dyld_objc_notify_register: dyld registration

① _dyld_objc_notify_register方法

  • The declaration of the _dyld_objc_notify_register method is as follows:
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

② From the above analysis, we can get:

  • Only for objc runtime use;
  • Register the handler to be called when mapping, unmapping and initializing the objc image;
  • Dyld will call back the mapped function through an array of image files containing objc-image-info.

③ The meanings of the three parameters in the method are as follows:

  • map_images: When dyld loads an image (image file) into the memory, it will trigger this function;
  • load_image: dyld initialization image will trigger this function;
  • unmap_image: This function is triggered when dyld removes the image.

The association between dyld and ObjC

To be continued

Guess you like

Origin blog.csdn.net/Forever_wj/article/details/109213838