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. indyld
Before we call our static constructor,libc
Will be called_objc_init()
So we have to do it ourselves.runtime_init()
:runtime
Runtime environment initialization, which is mainly:unattachedCategories
.allocatedClasses
, which will be analyzed in detail later.exception_init()
Initialization:libobjc
Exception 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 is
PrintHelp
,PrintOptions
Conditions can be printedThe environment variable
andhelp
. 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, where
OBJC_DISABLE_NONPOINTER_ISA
It’s something that we’re familiar with,nonpointer isa
The first is1
.
2. OBJC_DISABLE_NONPOINTER_ISA
The environment variable
To review the Nonpointer ISA section in code:
SSLPerson
It’s a class that we normally create, itsisa
The first is1
.
Let’s add the OBJC_DISABLE_NONPOINTER_ISA environment variable to the project:
Run the project again:
- Set up
OBJC_DISABLE_NONPOINTER_ISA
Later,isa
The first place becomes0
.SSLPerson
It is notnonpointer isa
.
3. OBJC_PRINT_LOAD_METHODS
The environment variable
Let’s add another environment variable, OBJC_PRINT_LOAD_METHODS, and run the program:
- As you can see, all of them
load
The methods are printed out.
4. export OBJC_HELP=1
printThe 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 pass
export OBJC_HELP=1
Command, also printed on the terminalThe environment variable
This 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_init
In the fileobjcFunc
Print first, and thenSSLPerson
In theload
Print, and finallymain.m
In thesslFunc
Printing. _objc_init
In 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_init
In the fileobjcFunc
Printed by662
theinits[i]();
This code executes, then the initialization is passedgetLibobjcInitializers
To complete.
Runtime_init Brief analysis
Enter the runtime_init:
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
Copy the code
unattachedCategories
Is to create theclassification
In the table.allocatedClasses
Is to create theclass
Of the table, and is opened upclass
.
Exception_init analysis
1. exception_init
Source 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_handler
Where 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 find
uncaught_handler
The initial value is assigned_objc_default_uncaught_exception_handler
, can also passobjc_setUncaughtExceptionHandler
Function to assign a value. - We’re going to give it to ourselves by the function
uncaught_handler
So we can listen when an exception occurs. - The system provides functions for the upper layer
NSSetUncaughtExceptionHandler()
Now let’s do it in code.
2. NSSetUncaughtExceptionHandler
Abnormal 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
ViewController
Is an array out of bounds code.AppDelegate
throughNSSetUncaughtExceptionHandler
Set the exception callback function to our ownSSLExceptionHandlers
Function.
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_terminate
It’s called.
_dyld_objc_notify_register analysis
- The next step is to
map_images
Functions 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
NXCreateMapTable
I created oneclass
This table is the master table, whether or not memory has been allocated.- about
Four thirds
The interpretation of the,Three quarters of
It’s usually used as a load factorOC class principle exploration: Cache structure analysis δΈcache
Analyzed 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 zero66
Then we have to apply66 times 4/3 is 88
The 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
SEL
It 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,
sel
andsels[i]
The method names are allretain
But their addresses are0x00007fff7bc03850
and0x00000001004b2c5b
Whysel
Assigned tosels[i]
? sels[i]
The value of theta is theta_getObjc2SelectorRefs
From the table, the data in the table is fromMachO
In,MachO
There are relative displacement addresses and offset addresses.sel
Is in thedyld
When I read it out,dyld
It links the entire program. We’re going todyld
And whenSEL
If 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:
- in
3632
Line, printcls
The value is0x00000001004d06a0
This is an address. in3634
Line, printcls
The value isOS_object
Has become the class name. - This shows that in
readClass
In the function, it should be doneclass
andaddress
I’m going to do something about itreadClass
Analyze.
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 class
SSLPerson
Next add the code, and judge yesSSLPerson
Class, 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 enter
3357
Line, there are many articles in this code online analysis, said that this time will enter, in fact, is not correct.
Debug to single step:
addNamedClass
Function willThe name of the class
andaddress
I’m going to associate, and then I’m going tocls
Add to the hash table.
Continue down:
Breakpoints enter the addClassTableEntry function:
- You can see that this is going to be
MetaClass
Also added to the hash table.
Continue down:
- Finally came to
return cls
The function has ended, but it doesn’tro
andrw
Operations related to,ro
andrw
Will be explored in the next article.
Questions can be exchanged in the comments section, click a like to support it!! π π π