In the loading process of DYLD, we combed out the loading process of DYLD. The following is a detailed introduction of the association between DYLD and OBJC.
objc_init()
Source analysis
First, take a look at the source of the _objc_init method in libObjc
Objc_init () :
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
// Read environment variables that affect the runtime, and open the environment variable help export OBJC_HELP = 1 if necessary
environ_init();
// Bindings for thread keys, such as thread data destructors
tls_init();
// run the C++ static constructor, libc calls _objc_init() before dyld calls our static destructor, so we have to do it ourselves
static_init();
// The runtime runtime environment is initialized with unattachedCategories and allocatedClasses
runtime_init();
// Initialize libobJC's exception handling system
exception_init();
// The cache condition is initialized
cache_init();
// Start the callback mechanism. Normally this doesn't do much because all initialization is lazy, but for some processes we can't wait to load trampolines dylib
_imp_implementationWithBlock_init();
/* _DYLD_OBJC_notify_register -- a place where dyld is registered -- for use only by OBJC runtime -- registers handlers for use when mapping, unmapping, and initializing objC mirror files, Dyld will use an array of image files containing objc_image_info, calling the mapped function map_images: Load_images: dyld initializes the image. Unmap_image: DyLD removes the image. */
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
Copy the code
The functions of each method are briefly introduced below:
-
environ_init()
Initialization of environment variables. We can print the environment variable, and then inXcodeEnvironment variables and path Settings, convenient for our specific debugging.Source debugging tips: We can take out the conditional statement content in order to see the print. Let’s say we want to look at all the environment variables426
–430
Get the code out.void environ_init(void) { / /... Omitting logic if (PrintHelp || PrintOptions) { / /... Omitting logic 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); }}}Copy the code
Alternatively, export OBJC_HELP = 1 can be used to print environment variables. See common environment variables here
-
Tls_init () is used to initialize and destruct thread pools.
void tls_init(void){#if SUPPORT_DIRECT_THREAD_KEYS// Local thread pool for processing pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);/ / the initial init #else _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);/ / destructor #endif } Copy the code
-
Static_init () runs system-level C++ static constructors. Libc calls _objc_init() before dyld calls our static constructor, that is, the system-level C++ constructor 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](); }}Copy the code
-
Runtime_init () initialization of the runtime environment is divided into two parts: class initialization and table initialization of the class (more on the corresponding functions later)
void runtime_init(void) { objc::unattachedCategories.init(32); objc::allocatedClasses.init(); // Initialize -- table of opened classes } Copy the code
-
Exception_init () initialize libobJC exception processing system is mainly initialized libobJC exception processing system, register exception processing callback, so as to monitor the exception processing, the source code is as follows:
void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); } Copy the code
- When you have
crash
(A crash is an instruction that is not allowed by the system, and then a signal that is given by the system)_objc_terminate
Method, walk touncaught_handler
Throw exceptions/*********************************************************************** * _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);// Throw an exception (*old_terminate)(); } @catch(...). {// It's not an objc object. Continue to C++ terminate.(*old_terminate)(); }}}Copy the code
- search
uncaught_handler
The default is_objc_default_uncaught_exception_handler
A function is passed in the app layer to handle the exception so that the function can be called, and then back to the original App layer, as shown below, wherefn
Is the function passed in, i.euncaught_handler
Is equal to thefn
objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) { // fn is the function passed in for the exception handle objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; / / assignment return result; } Copy the code
-
Classification of crash
The main reason for crash is that unprocessed signals are received, mainly from three places: kernel kernel, other processes and App itself. There are also three corresponding types:
-
Mach exceptions: These are the lowest level kernel-level exceptions. User developers can catch Mach exceptions by setting exception ports for Thread, task, and host directly through the Mach API.
-
Unix signals: Also known as BSD signals, if the developer does not catch a Mach exception, the exception is converted to the corresponding Unix signal by the host layer method ux_Exception () and sent to the error thread via the threadSignal () method. Single can be caught using the method Signal (x, SignalHandler).
-
NSException Application-level exception: It is an uncaught Objective-C exception that causes the program to send itself a SIGABRT signal and crash. For an uncaught Objective-C exception, you can catch it with a try catch. To capture or through NSSetUncaughtExceptionHandler () mechanism.
- for
Application level anomaly
, you can register the function caught by the exception, i.eNSSetUncaughtExceptionHandler
Mechanism to achieve thread alive, upload crash log collection. For crash interception processing, that is, an exception handle is given in app codeNSSetUncaughtExceptionHandler
, pass a function to the system, when the exception occurs, call the function (Function to thread alive, collect, and upload crash logs
), and then return to the original app layer, which is essentially aThe callback function
, as shown in the figure below:
The above methods are only suitable for collectionApplication level anomaly
What we have to do isReplace this ExceptionHandler with a custom function
Can.
- for
-
-
- When you have
-
Cache_init () Initializes the cache condition
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++; } // Register a set of rebootable caches for the current task 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 } Copy the code
-
_imp_implementationWithBlock_init() enables the callback mechanism. Usually this doesn’t do much because all initialization is lazy, but for some processes we can’t wait to load trampolines dylib.
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. // Desire to load libobjc-trampolines.dylib in some processes. Some programs (most notably QtWebEngineProcess, used by earlier versions of embedded Chromium) enable heavily restricted sandbox configuration files that prevent access to this dylib. If there is any action that calls the imp_implementationWithBlock (such as the one AppKit starts), then we will crash when we try to load it. Loading it here sets up and blocks the sandbox configuration file before it is enabled. // 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 } Copy the code
-
_dyld_objc_notify_register dyld registered
The implementation of this method is described in the dyLD source code. The following is a declaration of the _DYLD_OBJC_notify_register method:
// // 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); Copy the code
From the notes, it can be concluded that:
- Only for
Objc runtime
use Registration handler
To be called when mapping, unmapping, and initializing objC imagesdyld
The callback will be via an array containing objC-image-info’s image filemapped
function
The meanings of the three parameters in the method are as follows:
mapped
: dyld This function is triggered when an image file is loaded into memoryinit
This function is triggered when dyld initializes the imageunmapped
: this function is triggered when dyld removes the image
Let’s expand the relationship between DYLD and Objc separately
- Only for
The association between DYLD and Objc
The source code implementation and invocation of the method are as follows, that is, the association between DYLD and Objc can be reflected through the source code
===> DYLD source code - concrete implementationvoid _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped); } 🔽 ===> libobJC source -- call _dyLD_OBJC_notify_register (&map_images, load_images, unmap_image);Copy the code
It follows from the above
mapped
Is equivalent tomap_images
init
Is equivalent toload_images
unmapped
Is equivalent tounmap_image
inIOS – Underlying Principle 15: DyLD loading processMiddle, we knowload_images
Is in thenotifySingle
Method, throughsNotifyObjCInit
Call, as shown below:And then by looking upsNotifyObjCInit
“And finally found it_dyld_objc_notify_register --> registerObjCNotifiers
, in this method_dyld_objc_notify_register
The parameters passed in are assigned to three callback methods:So we have the following equivalence
sNotifyObjCMapped
= =mapped
= =map_images
sNotifyObjCInit
= =init
= =load_images
sNotifyObjCUnmapped
= =unmapped
= =unmap_image
Libobjc source code _dyLD_OBJC_notify_register (&map_images, load_images, unmap_image) is focused on two of the parameters. Map_images and load_images. Load_images call timing has been explained in dyLD loading process, the following uses map_images as an example to see when map_images call.
Map_images call timing
- Global search in DYLD
sNotifyObjcMapped
:registerObjCNotifiers -- notifyBatchPartial -- sNotifyObjCMapped
- Global search
notifyBatchPartial
In theregisterObjCNotifiers
Method call
Map_images is called before load_images, i.e. Map_images is called before load_images.
Dyld is associated with Objc
Combined with the DYLD loading process, the association between DYLD and Objc is shown as follows:
Description:
- in
Register callback functions in dyLD
Can be understood asAdd observer
- In objc
Dyld registered
Can be understood asSend a notification
Triggered the callback
Can be understood asExecute notification selector
reference
This article learns and references iOS- Underlying Principle 16: DyLD and OBJC association, thanks here
The article lists
List of blog posts