1. Debug code preparation

Download objC818 debuggable source code

2. Objc_init analysis

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, such as printing configuration logs
    environ_init(a);// Bindings for thread keys - such as per-thread destructors
    tls_init(a);// Run the C ++ static constructor. Before dyld calls our static constructor, 'libc' calls _objc_init(), so we have to do it ourselves
    static_init(a);/ / the runtime runtime environment initialization, it mainly: unattachedCategories, allocatedClasses later analysis
    runtime_init(a);// Initialize libobJC's exception handling system
    exception_init(a);#if __OBJC2__
    // The cache condition is initialized
    cache_t: :init(a);#endif
    // Start 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, right
    _imp_implementationWithBlock_init();
     // Register notifications with map_images(), load_images()
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    // map_images()
    // load_images()
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

2.1. Initialize environment variables environ_init()

Environment variables control log output during debugging

Environ_init

// _objc_inform is printed only when PrintHelp or PrintOptions are met
 if(PrintHelp || PrintOptions) { ..... Omit some codefor (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

Remove the if judgment

 for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            _objc_inform("%s: %s", opt->env, opt->help);
            _objc_inform("%s is set", opt->env);
  }
Copy the code

Running the program prints a number of environment variables like this:

objc[3443]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[3443]: OBJC_PRINT_IMAGES is set
objc[3443]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[3443]: OBJC_PRINT_IMAGE_TIMES is set
objc[3443]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[3443] :OBJC_PRINT_LOAD_METHODS is set
objc[3443] :OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[3443] :OBJC_PRINT_INITIALIZE_METHODS is set
objc[3443] :OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[3443] :OBJC_PRINT_RESOLVED_METHODS is set
objc[3443] :OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[3443] :OBJC_PRINT_CLASS_SETUP is set
objc[3443] :OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[3443] :OBJC_PRINT_PROTOCOL_SETUP is set
objc[3443] :OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[3443] :OBJC_PRINT_IVAR_SETUP is set
objc[3443] :OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[3443] :OBJC_PRINT_VTABLE_SETUP is set
objc[3443] :OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[3443] :OBJC_PRINT_VTABLE_IMAGES is set
objc[3443] :OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[3443] :OBJC_PRINT_CACHE_SETUP is set
objc[3443] :OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[3443] :OBJC_PRINT_FUTURE_CLASSES is set
objc[3443] :OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[3443] :OBJC_PRINT_PREOPTIMIZATION is set
objc[3443] :OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[3443] :OBJC_PRINT_CXX_CTORS is set
objc[3443] :OBJC_PRINT_EXCEPTIONS: log exception handling
objc[3443] :OBJC_PRINT_EXCEPTIONS is set
objc[3443] :OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw(a)objc[3443] :OBJC_PRINT_EXCEPTION_THROW is set
objc[3443] :OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[3443] :OBJC_PRINT_ALT_HANDLERS is set
objc[3443] :OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[3443] :OBJC_PRINT_REPLACED_METHODS is set
objc[3443] :OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[3443] :OBJC_PRINT_DEPRECATION_WARNINGS is set
objc[3443] :OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[3443] :OBJC_PRINT_POOL_HIGHWATER is set
objc[3443] :OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
objc[3443] :OBJC_PRINT_CUSTOM_CORE is set
objc[3443] :OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
objc[3443] :OBJC_PRINT_CUSTOM_RR is set
objc[3443] :OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
objc[3443] :OBJC_PRINT_CUSTOM_AWZ is set
objc[3443] :OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[3443] :OBJC_PRINT_RAW_ISA is set
objc[3443] :OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[3443] :OBJC_DEBUG_UNLOAD is set
objc[3443] :OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[3443] :OBJC_DEBUG_FRAGILE_SUPERCLASSES is set
objc[3443] :OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[3443] :OBJC_DEBUG_NIL_SYNC is set
objc[3443] :OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[3443] :OBJC_DEBUG_NONFRAGILE_IVARS is set
objc[3443] :OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[3443] :OBJC_DEBUG_ALT_HANDLERS is set
objc[3443] :OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place.which may be a leak
objc[3443] :OBJC_DEBUG_MISSING_POOLS is set
objc[3443] :OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order.and allow heap debuggers to track autorelease pools
objc[3443] :OBJC_DEBUG_POOL_ALLOCATION is set
objc[3443] :OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[3443] :OBJC_DEBUG_DUPLICATE_CLASSES is set
objc[3443] :OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[3443] :OBJC_DEBUG_DONT_CRASH is set
objc[3443] :OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated
objc[3443] :OBJC_DEBUG_POOL_DEPTH is set
objc[3443] :OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[3443] :OBJC_DISABLE_VTABLES is set
objc[3443] :OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[3443] :OBJC_DISABLE_PREOPTIMIZATION is set
objc[3443] :OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[3443] :OBJC_DISABLE_TAGGED_POINTERS is set
objc[3443] :OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[3443] :OBJC_DISABLE_TAG_OBFUSCATION is set
objc[3443] :OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[3443] :OBJC_DISABLE_NONPOINTER_ISA is set
objc[3443] :OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[3443] :OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[3443] :OBJC_DISABLE_FAULTS: disable os faults
objc[3443] :OBJC_DISABLE_FAULTS is set
objc[3443] :OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[3443] :OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[3443] :OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[3443] :OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[3443] :OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[3443] :OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set
Copy the code

Output environment variables that can be used to query configurations. You can also use terminals to output environment variables:

How do you use environment variables?

For example, use OBJC_DISABLE_NONPOINTER_ISA. NONPOINTER_ISA = 1; nonpointer = 1;

Configure environment variablesNONPOINTER_ISA.Edit Scheme > Arguments

It becomes a save pointer

Another example is configuration printingloadMethod environment variablesOBJC_PRINT_LOAD_METHODS Print out implementationloadSource of method

2.2. Static_init () calls the global constructor in the objC library before dyld

Define a global constructor here

LLDB debugging

This is where the global constructor is called, not indyldPhase calls.

2.3.tls_init() bindings for thread keys

2.4. Static_init () operationC++Static constructor

Libc calls _objc_init() before DYLD calls our static constructor, so we have to do it ourselves

2.5.runtime_init() initializes two tables

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    // The table has already been opened
    objc::allocatedClasses.init();
}
Copy the code

2.6. Exception_init () Exception Handling System initialization

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code
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.
            // An exception occurs where uncaught_handler is called
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch(...). {// It's not an objc object. Continue to C++ terminate.(*old_terminate)(); }}}Copy the code

Uncaught_handler is called when an exception occurs, which is a callback to which exception interception is implemented by assigning a value.

objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    returnresult; } ` objc_setUncaughtExceptionHandler ` is quite so ` uncaught_handler `Copy the code

We used the LGUncaughtExceptionHandle exception to intercept

2.6.1. Registration interception is required first

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Register exception interception
    [LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
    return YES;
}
Copy the code

2.6.2. Achieve installUncaughtSignalExceptionHandler method

+ (void)installUncaughtSignalExceptionHandler{
    NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
void LGExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
}
Copy the code

NSSetUncaughtExceptionHandler objc_setUncaughtExceptionHandler is equivalent to the upper code

Exceptions are intercepted by LGExceptionHandlers.

LGUncaughtExceptionHandle complete code will be posted at the end of the article.

2.7. Cache_init () Initialization of cache conditions

2.8. _dyld_objc_notify_register

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
Copy the code

_dyLD_OBJC_notify_register has three parameters in _objc_init

  • map_images: Manages all symbols in files and dynamic libraries (class.Protocol.selector.category)
  • load_images: Load executionloadmethods
  • unmap_image:dyldRelease resources

2.8.1. Map_images

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

The map_images_NOLock function is quite large, with more than a hundred lines, focusing on _read_images

2.8.2 _read_images

The _read_images code is over three hundred lines long. Here’s the overall flow:

_read_images Overall process analysis

_read_imagesThe overall process is as follows:

1. Conditional control for one load

2. Fix a mess with @selector at compile time

3. Error clutter class handling

4. Fix some messages

5. When we have a protocol in our class: readProtocol

6. Repair the protocol that is not loaded

7. Classify

8. Class loading processing

9. Unprocessed classes optimize those that are violated

_read_images Critical process analysis

1. Determine a load and create a total table

gdb_objc_realized_classesIs a general table, whether implemented or notruntime_initIn the tableallocatedClassesIt’s a table that’s already been opened up

2. Fix @selector confusion during precompilation

twoselThe address is different,_getObjc2SelectorRefsIt’s read from the tablemachoIn, there is a relative address and an offset address, which will change,sel_registerNameNoLockisdyldRead out, is the link to the whole program, to it, so you want to put the tableselReplace it with the one after the linksel.

3. Mishandled class handling

There is a rule in memory that when the memory of a class is moved, the original memory will be deleted. If it is not deleted, the above processing is required.

ReadClass analysis

Add some judgment code to the readClass to study only the loading of the LGPerson class

And then we look down

clsandnameAdd to the master table. In order tokey-valueForm join,keyisname.valueiscls.

With readClass processing, you know which class the address is, but you don’t know any more details, so you can take a tentative approach.

Class implementation location

According to the analysis of the overall process of MAP_images, we can know the loading of the class in step 8 and step 9. Find the relevant location and add the test code:

const char *mangledName = cls->nonlazyMangledName();
// If it is LGPerson, print the call information
const char *LGPersonName = "LGPerson";
  if (strcmp(mangledName, LGPersonName) == 0) {
   printf("%s Realize newly-resolved future classes: - %s\n",__func__,mangledName);
 }
Copy the code

LGPeronClass to addloadMethod, the breakpoint first breaks on a graph. The focus is on the methods that implement the classrealizeClassWithoutSwift. ifLGPeronClass is not addedloadMethod is also calledrealizeClassWithoutSwift. Add trial code, and breakpoint debugging, print call stack:

Call order:lookUpImpOrForward >initializeAndMaybeRelock>realizeClassMaybeSwiftMaybeRelock>realizeClassWithoutSwift

Next, you can analyze the implementation of the class realizeClassWithoutSwift method

3. LGUncaughtExceptionHandle source file

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LGUncaughtExceptionHandle : NSObject

@property (nonatomic) BOOL dismissed;

+ (void)installUncaughtSignalExceptionHandler;

@end

NS_ASSUME_NONNULL_END

Copy the code
#import "LGUncaughtExceptionHandle.h"
#import <SCLAlertView.h>
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#include <stdatomic.h>


NSString * const LGUncaughtExceptionHandlerSignalExceptionName = @"LGUncaughtExceptionHandlerSignalExceptionName";
NSString * const LGUncaughtExceptionHandlerSignalExceptionReason = @"LGUncaughtExceptionHandlerSignalExceptionReason";
NSString * const LGUncaughtExceptionHandlerSignalKey = @"LGUncaughtExceptionHandlerSignalKey";
NSString * const LGUncaughtExceptionHandlerAddressesKey = @"LGUncaughtExceptionHandlerAddressesKey";
NSString * const LGUncaughtExceptionHandlerFileKey = @"LGUncaughtExceptionHandlerFileKey";
NSString * const LGUncaughtExceptionHandlerCallStackSymbolsKey = @"LGUncaughtExceptionHandlerCallStackSymbolsKey";


atomic_int      LGUncaughtExceptionCount = 0;
const int32_t   LGUncaughtExceptionMaximum = 8;
const NSInteger LGUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger LGUncaughtExceptionHandlerReportAddressCount = 5;


@implementation LGUncaughtExceptionHandle

/// Exception
void LGExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    
    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
    if (exceptionCount > LGUncaughtExceptionMaximum) {
        return;
    }
    // Get stack information - model programming ideas
    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
    [userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
    
    [[[LGUncaughtExceptionHandle alloc] init]
     performSelectorOnMainThread:@selector(lg_handleException:)
     withObject:
     [NSException
      exceptionWithName:[exception name]
      reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
    
}

+ (void)installUncaughtSignalExceptionHandler{
    NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}


- (void)lg_handleException:(NSException *)exception{
    // Save the upload server
    
    NSDictionary *userinfo = [exception userInfo];
    [self saveCrash:exception file:[userinfo objectForKey:LGUncaughtExceptionHandlerFileKey]];
    
    SCLAlertView *alert = [[SCLAlertView alloc] initWithNewWindowWidth:300.f];
    [alert addButton:@ "crash" actionBlock:^{
        self.dismissed = YES;
    }];
    [alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0.0f];
}

/// Save the crash information or upload it
- (void)saveCrash:(NSException *)exception file:(NSString *)file{
    
    NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];// Exception stack information
    NSString *reason = [exception reason];// The cause of the exception
    NSString *name = [exception name];// Exception name
    
    // Or enter the crash information directly in code to further analyze the cause of the error in the console
    // NSLog(@"crash: %@", exception);
    
    NSString * _libPath  = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory.NSUserDomainMask.YES) objectAtIndex:0] stringByAppendingPathComponent:file];
    
    if(! [[NSFileManager defaultManager] fileExistsAtPath:_libPath]){
        [[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a=[dat timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    
    NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
    
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason: %@\nException name: %@\nException stack: %@",name, reason, stackArray];
    
    BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    NSLog(Sucess :%d,%@,sucess,savePath);
    
}

// get function stack information
+ (NSArray *)lg_backtrace{
    
    void* callstack[128];
    int frames = backtrace(callstack, 128);// To get the current thread's function call stack, return the actual number of Pointers fetched
    char **strs = backtrace_symbols(callstack, frames);// The information obtained from backtrace is converted to an array of strings
    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (i = LGUncaughtExceptionHandlerSkipAddressCount;
         i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;
         i++)
    {
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    return backtrace;
}
@end
Copy the code