preface

In the previous iOS Underlying Principles: Application Loading, we mainly analyzed the loading process of dyld. Today, we will mainly analyze objc_init and read_images to explore the loading of classes.

The preparatory work

  • Objc4-818.2 – the source code

First, objc_init analysis

Enter the objc_init function:

  • environ_init(): Reads environment variables that affect the runtime, and prints environment variable help if needed.
  • tls_init(): About threadskey– such as the per-thread data destructor, which will be analyzed later.
  • static_init()Run:C++Static constructors. indyldBefore we call our static constructor,libcWill be called_objc_init()So we have to do it ourselves.
  • runtime_init():runtimeRuntime environment initialization, which is mainly:unattachedCategories.allocatedClasses, which will be analyzed in detail later.
  • exception_init()Initialization:libobjcException handling system.
  • cache_init(): Cache condition is initialized.
  • _imp_implementationWithBlock_init: Starts the callback mechanism. Usually this doesn’t do anything, because all the initialization is lazy, but for some processes, we can’t wait to load, righttrampolines dylib.

Environ_init analysis

Enter the environ_init function:

  • As shown in the picture above, the part circled in red isPrintHelp,PrintOptionsConditions can be printedThe environment variableandhelp. So let’s just copy this code, get rid of the constraints, print it out and see what we can print out.

1. Source print environment variables

Copy the code above and run the project:

  • You can see that a number of environment variables are printed out, whereOBJC_DISABLE_NONPOINTER_ISAIt’s something that we’re familiar with,nonpointer isaThe first is1.

2. OBJC_DISABLE_NONPOINTER_ISAThe environment variable

To review the Nonpointer ISA section in code:

  • SSLPersonIt’s a class that we normally create, itsisaThe first is1.

Let’s add the OBJC_DISABLE_NONPOINTER_ISA environment variable to the project:

Run the project again:

  • Set upOBJC_DISABLE_NONPOINTER_ISALater,isaThe first place becomes0.SSLPersonIt is notnonpointer isa.

3. OBJC_PRINT_LOAD_METHODSThe environment variable

Let’s add another environment variable, OBJC_PRINT_LOAD_METHODS, and run the program:

  • As you can see, all of themloadThe methods are printed out.

4. export OBJC_HELP=1printThe environment variable

It is inconvenient to print environment variables through the source code. Here is the terminal to print environment variables:

  • Enter the project directory and passexport OBJC_HELP=1Command, also printed on the terminalThe environment variableThis way is quicker and more convenient.

Static_init analysis

Let’s start by adding some code. Add C++ functions to the _objc_init file and print objcFunc. Add the load method to the SSLPerson class and print the load. Add the C++ function to main.m and print sslFunc. Then run the program:

  • Based on the results,_objc_initIn the fileobjcFuncPrint first, and thenSSLPersonIn theloadPrint, and finallymain.mIn thesslFuncPrinting.
  • _objc_initIn the fileC++The function is called first, so where is it called? Let’s look at that.

At the break point in static_init, run the program:

  • Based on the results of this operation,_objc_initIn the fileobjcFuncPrinted by662theinits[i]();This code executes, then the initialization is passedgetLibobjcInitializersTo complete.

Runtime_init Brief analysis

Enter the runtime_init:

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
Copy the code
  • unattachedCategoriesIs to create theclassificationIn the table.allocatedClassesIs to create theclassOf the table, and is opened upclass.

Exception_init analysis

1. exception_initSource code analysis

Let’s look at the implementation of exception_init:

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code

And I’m going to go to _objc_terminate:

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)(); }}}Copy the code
  • You can see that the system has caught an exception, which is called when it is caught(*uncaught_handler)((id)e)This code, let’s look at it nextuncaught_handlerWhere the value is assigned.

Global search uncaught_handler:

static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
Copy the code
  • You can finduncaught_handlerThe initial value is assigned_objc_default_uncaught_exception_handler, can also passobjc_setUncaughtExceptionHandlerFunction to assign a value.
  • We’re going to give it to ourselves by the functionuncaught_handlerSo we can listen when an exception occurs.
  • The system provides functions for the upper layerNSSetUncaughtExceptionHandler()Now let’s do it in code.

2. NSSetUncaughtExceptionHandlerAbnormal monitoring

To create an iOS project, add code to appdelegate.m and viewController.m:

@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary  *)launchOptions { NSSetUncaughtExceptionHandler(&SSLExceptionHandlers); return YES; } // Exception void SSLExceptionHandlers(NSException * Exception) {NSLog(@" an Exception occurred "); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSArray *dataArray = @[@"Hank",@"CC",@"Kody",@"Cooci",@"Cat"]; NSLog(@"%@",dataArray[5]); } @endCopy the code
  • ViewControllerIs an array out of bounds code.AppDelegatethroughNSSetUncaughtExceptionHandlerSet the exception callback function to our ownSSLExceptionHandlersFunction.

Run the program:

  • The exception code really goes here, and it proves that we should be right. Let’s take a look at the stack.

  • Look at the stack and see that it does pass_objc_terminateIt’s called.

_dyld_objc_notify_register analysis

  • The next step is tomap_imagesFunctions are analyzed for pointcuts.

2. Introduction of read_images process

Click to enter the map_images function:

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
Copy the code

Map_images_NOLock

void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { ... If (firstTime) {preopt_init(); } _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); }Copy the code
  • The point of this function is_read_images, and then the analysis.

3. Read_images Body flow

Enter the read_images function:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // 1: The condition control performs the first load if (! doneOnce) {... } // 2: Fix the '@selector' mess in precompile; static size_t UnfixedSelectors; {... } // 3: error messy class handling for (EACH_HEADER) {... } // 4: fix remapping some classes that were not loaded by the image file if (! noClassesRemapped()) {... } // 5: fix some messages! for (EACH_HEADER) {... } // 6: when there is a protocol in our class: readProtocol for (EACH_HEADER) {... } // 7: fix not loaded protocol for (EACH_HEADER) {... } / / 8: classification of processing the if (didInitialAttachCategories) {... } // 9: class loading processing for (EACH_HEADER) {... } // 10: if (resolvedFutureClasses){... }}Copy the code

Iv. Introduction of readClass core

For the read_images function, let’s do some detailed analysis.

The condition controls the first load

View the implementation code:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // 1: Conditional control is performed once by loading if (! DoneOnce) {/ / small object types of some processing initializeTaggedPointerObfuscator (); // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); }}Copy the code
  • NXCreateMapTableI created oneclassThis table is the master table, whether or not memory has been allocated.
  • aboutFour thirdsThe interpretation of the,Three quarters ofIt’s usually used as a load factorOC class principle exploration: Cache structure analysis δΈ­cacheAnalyzed during capacity expansion. It’s the same principle here, but it’s the other way around, so let’s say the number we’re actually going to use is zero66Then we have to apply66 times 4/3 is 88The number of magnitudes.

Fixed @selector confusion during precompile

View the implementation code:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // 2: Fixed '@selector' confusion during precompilation // Fix up @selector references static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) continue; bool isBundle = hi->isBundle(); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i < count; i++) { const char *name = sel_cname(sels[i]); SEL sel = sel_registerNameNoLock(name, isBundle); if (sels[i] ! = sel) { sels[i] = sel; } } } } }Copy the code
  • SELIt is a method name, but it also has a memory address. The code in this section will allocate and fix the method with the same name.

  • Based on the print,selandsels[i]The method names are allretainBut their addresses are0x00007fff7bc03850and0x00000001004b2c5bWhyselAssigned tosels[i]?
  • sels[i]The value of theta is theta_getObjc2SelectorRefsFrom the table, the data in the table is fromMachOIn,MachOThere are relative displacement addresses and offset addresses.
  • selIs in thedyldWhen I read it out,dyldIt links the entire program. We’re going todyldAnd whenSELIf not, redirect.

Error cluttered class handling readClass introduction

View the implementation code:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // 3: For (EACH_HEADER) {if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; } classref_t const *classlist = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); if (newCls ! = CLS && newCls) {// Some classes have been deleted, // Class was moved but not deleted. Currently this occurs // only when the new Class resolved a future class. // Non-lazily realize the class below. resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}}Copy the code

Breakpoint debugging:

  • in3632Line, printclsThe value is0x00000001004d06a0This is an address. in3634Line, printclsThe value isOS_objectHas become the class name.
  • This shows that inreadClassIn the function, it should be doneclassandaddressI’m going to do something about itreadClassAnalyze.

5. ReadClass analysis

Enter the readClass:

Add code to the function to print all methods:

  • All the methods are printed, and at the end we see our own classSSLPersonNext add the code, and judge yesSSLPersonClass, the breakpoint is broken for debugging.

Add conditional control code and breakpoints:

Run the program:

  • Once the breakpoint is here, continue adding breakpoints below.

Add breakpoints at 3357 and 3384:

Breakpoint continue:

  • The breakpoint did not enter3357Line, there are many articles in this code online analysis, said that this time will enter, in fact, is not correct.

Debug to single step:

  • addNamedClassFunction willThe name of the classandaddressI’m going to associate, and then I’m going toclsAdd to the hash table.

Continue down:

Breakpoints enter the addClassTableEntry function:

  • You can see that this is going to beMetaClassAlso added to the hash table.

Continue down:

  • Finally came toreturn clsThe function has ended, but it doesn’troandrwOperations related to,roandrwWill be explored in the next article.

Questions can be exchanged in the comments section, click a like to support it!! πŸ˜„ πŸ˜„ πŸ˜„