美文网首页
iOS之异常处理及信号

iOS之异常处理及信号

作者: cj3479 | 来源:发表于2020-04-22 23:37 被阅读0次

    跟Java一样,iOS也有try catch,抓取某些代码的异常,防止某些crash
    Mach是Mac OS和iOS操作系统的微内核核心,Mach异常是指最底层的内核级异常 。每个thread,task都有一个异常端口数组,Mach的部分API暴露给了开发者,开发者可以直接通过Mach API设置thread,task,host的异常端口,来监听捕获Mach异常,抓取Crash事件。所以当APP中产生异常时,最先能监听到异常的就是Mach
    iOS的异常也称为Mach异常
    比如数组越界异常NSRangeException([__NSArrayI objectAtIndexedSubscript:]: index 3 beyond bounds [0 .. 1]'),
    可变字典中插入空对象异常NSInvalidArgumentException([__NSDictionaryM setObject:forKey:]: object cannot be nil (key: aa))等等,
    像这样的NSException是可以被try catch住的,在iOS中除了使用try catch以外,最典型的是使用UncaughtExceptionHandler机制捕获,
    具体调用如下
    void HandleException(NSException *exception){
    }
    这个可以捕获整个进程的异常,不需要像try catch那样分批捕获,这也是很多崩溃统计sdk的原理

    当前iOS中很多crash是不能被try catch,比如内存原因产生的crash,最典型的是使用一个已经释放的内存,会产生EXC_BAD_ACCESS的错误,并且发出产生SIGSEGV(信号)这样的信号,当这样crash是可以用另一种方式捕获的,那就是signal机制,它来捕获Crash发生时的错误内容,具体用法
    signal(SIGSEGV, SignalHandler);
    void SignalHandler(int signal){
    //处理异常信号
    }
    注册对SIGSEGV监听,当SIGSEGV信号产生时,触发SignalHandler函数的调用

    说明:上面也说过,在捕获Crash事件时,如果mach可以处理crash,NSExcetion导致的crash,优选Mach异常。因为Mach异常处理会先于Unix信号处理发生,如果Mach异常的handler让程序exit了,那么Unix信号就永远不会到达这个进程了,如果mach不处理的话,那么NSExcetion会转化为相应的信号SIGABRT,传递出去产生crash,

    iOS开发者常见的 UNIX 信号 如下:

    UNIX 信号 注释
    SIGSEGV 访问无效的内存地址。地址存在,但是应用程序无法访问。
    SIGABRT 程序崩溃。由 C函数 abort() 初始化。通常意味着系统检测到某些事务出错,例如 assert() 或者 NSAssert() 校验失败。
    SIGBUS 访问无效的内存地址。地址不存在,或对齐无效。(The address does not exist, or the alignment is invalid.)
    SIGTRAP 调试器相关
    SIGILL 尝试执行非法的、有缺陷、未知的或者需要权限的指令。
    SIGKILL 程序结束接收中止信号。

    下面举例说明常见的一种crash,以下场景均未对异常和信号进行捕获

    1. 数组越界
    NSArray *testArray = @[@"11",@"22"];
    NSString *value = testArray[3];
    

    异常日志如下

    Exception Type:  EXC_CRASH (SIGABRT)
    Exception Codes: 0x0000000000000000, 0x0000000000000000
    Exception Note:  EXC_CORPSE_NOTIFY
    Triggered by Thread:  0
    
    Last Exception Backtrace:
    0   CoreFoundation                  0x18eb6ea48 __exceptionPreprocess + 220
    1   libobjc.A.dylib                 0x18e895fa4 objc_exception_throw + 55
    2   CoreFoundation                  0x18ebc4360 _CFThrowFormattedException + 111
    3   CoreFoundation                  0x18ea652a0 -[__NSArrayI objectAtIndexedSubscript:] + 127
    4   DSCrashDemo                   0x10256c4b8 -[ExceptionViewController testArrayOutOfIndex] + 50360 (ExceptionViewController.m:54)
    说明
    Exception Type:表示异常类型以及信号值,这里EXC_CRASH一般就是NSExcetion导致的crash,SIGABRT
    Exception Codes: 异常码,暂时不知具体的值表示什么含义
    Triggered by Thread:  发生crash的线程
    
    1. 访问已经释放的内存crash
      MRC下
      init_safe_free的实现见最底部
    AppDelegate.m
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        //AppDelegate里开启了init_safe_free(),表示释放的内存不会被覆盖,所以tempView调用setNeedsDisplay肯定会崩溃
        init_safe_free();
    }
    
    MyMrcView.m
    - (instancetype)init{
        UILabel *tempView = [UILabel new];
       [tempView release];
       NSLog(@"text=%@",tempView.text);
    }
    

    异常日志如下

    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
    Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000555555560
    VM Region Info: 0x555555560 is not in any region.  Bytes after previous region: 11632203105  
          REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
          MALLOC_NANO            0000000280000000-00000002a0000000 [512.0M] rw-/rwx SM=PRV  
    --->  
          UNUSED SPACE AT END
    
    Termination Signal: Segmentation fault: 11
    Termination Reason: Namespace SIGNAL, Code 0xb
    Terminating Process: exc handler [1817]
    Triggered by Thread:  0
    
    
    Thread 0 name:  Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0   libobjc.A.dylib                 0x000000018e892090 objc_msgSend + 16
    1   DSCrashDemo                     0x00000001007d6400 -[MyMrcView init] + 25600 (MyMrcView.m:30)
    2   DSCrashDemo                     0x00000001007d8cf0 -[MRCOneViewController crashSignalEGVClick] + 36080 (MRCOneViewController.m:37)
    3   UIKitCore                       0x0000000192bf59ac -[UIApplication sendAction:to:from:forEvent:] + 96
    说明
    Exception Type:表示异常类型以及信号值,这里EXC_BAD_ACCESS表示内存访问错误,SIGSEG表示内存段异常信号
    Exception Subtype: 表示不合法内存地址
    Termination Signal:同SIGSEG
    Terminating Process: 进程id
    Triggered by Thread:  发生crash的线程
    

    如果将代码改为下面代码

    AppDelegate.m
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        //AppDelegate里开启了init_safe_free(),表示释放的内存不会被覆盖,所以tempView调用setNeedsDisplay肯定会崩溃
        //init_safe_free();
    }
    
    MyMrcView.m
    - (instancetype)init{
        UILabel *tempView = [UILabel new];
        NSLog(@"tempView=%@",tempView);
       [tempView release];
        //AppDelegate里开启了init_safe_free(),表示释放的内存不会被覆盖,所以tempView调用setNeedsDisplay肯定会崩溃
        //如果不开启init_safe_free(),下面创建了10个UIView,那块被释放的内存可能会被覆盖,所以tempView调用setNeedsDisplay不一定会崩溃
            for (int i = 0; i < 5; i ++) {
                UILabel *temp = [[[UILabel alloc]init] autorelease];
                NSLog(@"temp=%@",temp);
                temp.text = [NSString stringWithFormat:@"UILabel_%d",i];
                [self addSubview:temp];
            }
       NSLog(@"text=%@",tempView.text);
    }
    

    测试了几次,其中存在crash的,也存在没crash
    没crash的日志如下,一目了然

    2020-04-22 21:42:13.999612+0800 DSCrashDemo[1910:563205] tempView=<UILabel: 0x103400480; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x282380550>>
    2020-04-22 21:42:14.000465+0800 DSCrashDemo[1910:563205] temp=<UILabel: 0x103400480; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x2823805f0>>
    2020-04-22 21:42:14.001157+0800 DSCrashDemo[1910:563205] temp=<UILabel: 0x103403b50; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x282380690>>
    2020-04-22 21:42:14.001728+0800 DSCrashDemo[1910:563205] temp=<UILabel: 0x103403dc0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x282380780>>
    2020-04-22 21:42:14.002311+0800 DSCrashDemo[1910:563205] temp=<UILabel: 0x103404030; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x282380870>>
    2020-04-22 21:42:14.002865+0800 DSCrashDemo[1910:563205] temp=<UILabel: 0x1034042a0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x282380960>>
    2020-04-22 21:42:14.014872+0800 DSCrashDemo[1910:563205] text=UILabel_0
    
    1. 中止进程信号
      代码如下,模拟主线程阻塞,然后杀死进程,有点像Android的ANR,因为iOS如果主线程阻塞的话不会ANR,所以当成crash处理
    82 - (void)bugThreeButtonClick{
    83 while (1) {
    84 }
    

    异常日志如下

    Exception Type:  EXC_CRASH (SIGKILL)
    Exception Codes: 0x0000000000000000, 0x0000000000000000
    Exception Note:  EXC_CORPSE_NOTIFY
    Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
    Termination Description: SPRINGBOARD, process-exit watchdog transgression: application<dasheng.DSCrashDemo.aa>:2094 exhausted real (wall clock) time allowance of 5.00 seconds | ProcessVisibility: Foreground | ProcessState: Running | WatchdogEvent: process-exit | WatchdogVisibility: Foreground | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 7.390 (user 7.390, system 0.000), 25% CPU", | "Elapsed application CPU time (seconds): 4.988, 17% CPU" | )
    Triggered by Thread:  0
    
    Thread 0 name:  Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0   DSCrashDemo                     0x000000010072088c -[DeadLockViewController bugThreeButtonClick] + 34956 (DeadLockViewController.m:83)
    1   UIKitCore                       0x0000000192bf59ac -[UIApplication sendAction:to:from:forEvent:] + 96
    2   UIKitCore                       0x000000019262bfbc -[UIControl sendAction:to:forEvent:] + 240
    3   UIKitCore                       0x000000019262c320 -[UIControl _sendActionsForEvents:withEvent:] + 408
    4   UIKitCore                       0x000000019262b33c -[UIControl touchesEnded:withEvent:] + 520
    5   UIKitCore                       0x00000001927dca58 _UIGestureEnvironmentUpdate + 7636
    
    1. 主线程死锁
    44 - (void)bugButtonClick{
    45      dispatch_sync(dispatch_get_main_queue(), ^{
    46        NSLog(@"任务2 - %@", [NSThread currentThread]); // 任务2
    47   });
    }
    

    异常日志如下

    xception Type:  EXC_BREAKPOINT (SIGTRAP)
    Exception Codes: 0x0000000000000001, 0x000000018e81f0e4
    Termination Signal: Trace/BPT trap: 5
    Termination Reason: Namespace SIGNAL, Code 0x5
    Terminating Process: exc handler [2145]
    Triggered by Thread:  0
    
    Application Specific Information:
    BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread
    Abort Cause 9005618706777092
    
    Thread 0 name:  Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0   libdispatch.dylib               0x000000018e81f0e4 __DISPATCH_WAIT_FOR_QUEUE__ + 448
    1   libdispatch.dylib               0x000000018e81ec74 _dispatch_sync_f_slow + 140
    2   libdispatch.dylib               0x000000018e81ec74 _dispatch_sync_f_slow + 140
    3   DSCrashDemo                     0x00000001001bc3bc -[DeadLockViewController bugButtonClick] + 33724 (DeadLockViewController.m:45)
    4   UIKitCore                       0x0000000192bf59ac -[UIApplication sendAction:to:from:forEvent:] + 96
    
    其中与“EXC_CRASH”非常相似,EXC_BREAKPOINT 也往往与调试器一起发挥作用,在测试阶段被捕获
    
    1. 非法内存
      代码如下
    - (void)crashSignalEGVClick{
        //EXC_BAD_ACCESS(code=1,address=0x1111):表示0x00001111此内存并不合法,SIGSEGV类型崩溃
        int *pi = (int*)0x00001111;
        *pi = 17;
    }
    

    异常日志

    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
    Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000001111
    VM Region Info: 0x1111 is not in any region.  Bytes before following region: 4365446895
          REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
          UNUSED SPACE AT START
    --->  
          __TEXT                 0000000104338000-0000000104348000 [   64K] r-x/r-x SM=COW  ...p/DSCrashDemo
    
    Termination Signal: Segmentation fault: 11
    Termination Reason: Namespace SIGNAL, Code 0xb
    Terminating Process: exc handler [2051]
    Triggered by Thread:  0
    
    Thread 0 name:  Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0   DSCrashDemo                     0x000000010433ef28 -[SEGVViewController crashSignalEGVClick] + 28456 (SEGVViewController.m:36)
    1   UIKitCore                       0x0000000192bf59ac -[UIApplication sendAction:to:from:forEvent:] + 96
    说明跟上面的访问已经释放的内存crash一样
    
    1. 内存地址未对齐,修改一个保护的地址
    - (void)crashSignalBUSClick{
        //SIGBUS,内存地址未对齐
        //EXC_BAD_ACCESS(code=1,address=0x1000dba58)
        //常量字符串不能被修改
        char *s = "hello";
        *ss = 'a';
    }
    

    异常日志

    Exception Type:        EXC_BAD_ACCESS (SIGBUS)
    Exception Codes:       KERN_PROTECTION_FAILURE at 0x000000010b2638b1
    Exception Note:        EXC_CORPSE_NOTIFY
    
    Termination Signal:    Bus error: 10
    Termination Reason:    Namespace SIGNAL, Code 0xa
    Terminating Process:   exc handler [67982]
    
    VM Regions Near 0x10b2638b1:
    --> __TEXT                 000000010b259000-000000010b266000 [   52K] r-x/r-x SM=COW  /Users/USER/Library/Developer/CoreSimulator/Devices/9D2C4E72-3C0E-45C7-B00C-77E0F2882038/data/Containers/Bundle/Application/081D3E2E-0AE4-4796-815D-AA443B8534DE/DSCrashDemo.app/DSCrashDemo
        __DATA                 000000010b266000-000000010b26a000 [   16K] rw-/rw- SM=COW  /Users/USER/Library/Developer/CoreSimulator/Devices/9D2C4E72-3C0E-45C7-B00C-77E0F2882038/data/Containers/Bundle/Application/081D3E2E-0AE4-4796-815D-AA443B8534DE/DSCrashDemo.app/DSCrashDemo
    
    Application Specific Information:
    CoreSimulator 704.12.1 - Device: iPhone 11 Pro Max (9D2C4E72-3C0E-45C7-B00C-77E0F2882038) - Runtime: iOS 13.4 (17E8260) - DeviceType: iPhone 11 Pro Max
    
    Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
    0   dasheng.DSCrashDemo.aa          0x000000010b262d5f -[BusViewController crashSignalBUSClick] + 31 (BusViewController.m:33)
    1   com.apple.UIKitCore             0x00007fff48bc9235 -[UIApplication sendAction:to:from:forEvent:] + 83
    
    说明字符串常量”hello”出现在一个表达式中时,表达式使用的值就是这些字符所存储的地址,而不是这些字符本身。这个常量对应的地址是无不可写的,是被保护的,想想看,如果ss = 'a'生效,那么该地址存储的值改变了,那么常量是不是也被改变了,不合法啊
    
    1. 内存错误,将一个对象类型的属性声明为assign
      说明一个属性是assign时,不管是ARC还是MRC,当给这个属性对象赋值时,引用计数都不会+1,可以了理解为weak
      代码如下
    @interface ExceptionViewController ()
    @property(nonatomic,assign)NSObject *testObj;
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        UIButton *btn3 = [UIButton new];
        btn3.frame = CGRectMake(100,320,100,100);
        [btn3 setTitle:@"ARC Assgin" forState:UIControlStateNormal];
        [btn3 addTarget:self action:@selector(testArcAssgin) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn3];
       
    47    NSObject *Object = [NSObject new];
    48    self.testObj = Object;
    49 }
    .....
    64  -(void)testArcAssgin{
    65    NSLog(@"testArcAssgin testObj=%p",self.testObj);
    66 }
    

    异常日志:

    Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
    Exception Codes:       EXC_I386_GPFLT
    Exception Note:        EXC_CORPSE_NOTIFY
    
    Termination Signal:    Segmentation fault: 11
    Termination Reason:    Namespace SIGNAL, Code 0xb
    Terminating Process:   exc handler [63007]
    
    Application Specific Information:
    CoreSimulator 704.12.1 - Device: iPhone 11 Pro Max (9D2C4E72-3C0E-45C7-B00C-77E0F2882038) - Runtime: iOS 13.4 (17E8260) - DeviceType: iPhone 11 Pro Max
    
    Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
    0   libobjc.A.dylib                 0x00007fff50aedc6a objc_retain + 10
    1   dasheng.DSCrashDemo.aa          0x000000010bca734c -[ExceptionViewController testArcAssgin] + 44 (ExceptionViewController.m:65)
    2   com.apple.UIKitCore             0x00007fff48bc9235 -[UIApplication sendAction:to:from:forEvent:] + 83
    3   com.apple.UIKitCore             0x00007fff4857a9d3 -[UIControl sendAction:to:forEvent:] + 223
    4   com.apple.UIKitCore             0x00007fff4857ad1b -[UIControl _sendActionsForEvents:withEvent:] + 396
    5   com.apple.UIKitCore             0x00007fff48579c8c -[UIControl touchesEnded:withEvent:] + 497
    
    说明:因为testObj是assign的属性,所以当48行代码,Object给testObj赋值时,Object的对应的引用计数并不增加,依然是1
    ,当viewDidLoad结束调用,函数出栈,释放局部变量Object,其引用计数-1,变成0了,所以当点击触发testArcAssgin函数时self.testObj的引用计数也为0了,导致crash
    
    1. 内存错误,将一个分类的对象类型的属性在getter和setter的时候设为OBJC_ASSOCIATION_ASSIGN
    UIView+TestName.h
    @class RootViewController;
    @interface UIView (TestName)
    @property (nonatomic, strong) RootViewController *vc;
    @end
    
    UIView+TestName.m
    -(void)setVc:(RootViewController *)vc
    {
        objc_setAssociatedObject(self, _cmd, vc, OBJC_ASSOCIATION_ASSIGN);
    }
    
    -(RootViewController *)vc
    {
        return objc_getAssociatedObject(self, @selector(setVc:));
    }
    

    异常日志:同8,这里就不细说了

    init_safe_free()具体实现

    DSSafeFree.m
    #import "DSSafeFree.h"
    #import "fishhook.h"
    #import <malloc/malloc.h>
    #import "TYLQueue.h"
    #import <libkern/OSAtomic.h>
    long unfreeSize = 0;
    TYLQueue *_unfreeQueue = NULL;
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    
    
    #define MAX_SAVE_MEM_SIZE 1024*1024*100 //最多存这么多值,大于这个值就释放一部分
    #define MAX_SAVE_MEM_NUM  1024*1024*10 //最多保留这么多指针,再多就释放一部分
    #define PER_FREE_NUM 100 //每次释放指针数量
    
    static int (*orig_free)(const void *);
    
    
    void free_some_mem(size_t freeNum){
        size_t count = tyl_queue_length(_unfreeQueue);
        freeNum = freeNum > count ? count : freeNum;
        for (int i = 0; i < freeNum; i++) {
            void *unfreePoint = tyl_queue_dequeue(_unfreeQueue);
            size_t memSize = malloc_size(unfreePoint);
            OSSpinLockLock(&spinLock);
            unfreeSize -= memSize;
            OSSpinLockUnlock(&spinLock);
            orig_free(unfreePoint);
        }
    }
    
    
    void safe_free(void* p){
    #if 0
        size_t memSize = malloc_size(p);
        memset(p, 0x55, memSize);
        orig_free(p);
    #else
        int unfreeCount = tyl_queue_length(_unfreeQueue);
        if (unfreeCount > MAX_SAVE_MEM_NUM * 0.9 || unfreeSize > MAX_SAVE_MEM_SIZE) {
            free_some_mem(PER_FREE_NUM);
        }
        size_t memSize = malloc_size(p);
        memset(p, 0x55, memSize);
        OSSpinLockLock(&spinLock);
        unfreeSize += memSize;
        OSSpinLockUnlock(&spinLock);
        tyl_queue_enqueue(_unfreeQueue, p);
    #endif
        return;
    }
    
    
    bool init_safe_free(){
        _unfreeQueue = tyl_queue_create_capacity(MAX_SAVE_MEM_NUM);
        rebind_symbols((struct rebinding[1]){{"free", safe_free,(void *)&orig_free},},1);
        return true;
    }
    
    
    @implementation DSSafeFree
    
    @end
    
    TYLQueue.c
    //  TYLQueue.c
    
    #include "TYLQueue.h"
    static void tyl_queue_realloc(TYLQueue *q);
    
    int tyl_queue_length(TYLQueue *q){
        return q->_size;
    }
    
    TYLQueue *tyl_queue_create(int priority){
        TYLQueue *q = (TYLQueue *)malloc(sizeof(TYLQueue));
        q->_priority = priority;
        q->_size = 0;
        q->_capacity = 11;//default initial value
        q->_node = (void **)malloc(sizeof(void*)*q->_capacity);
        return q;
    }
    
    TYLQueue *tyl_queue_create_capacity(long capacity){
        TYLQueue *q = (TYLQueue *)malloc(sizeof(TYLQueue));
        q->_priority = TYL_PRIORITY_DEFAULT;
        q->_size = 0;
        if (capacity > 0) {
            q->_capacity = capacity;
        }else{
            q->_capacity = 11;//default initial value
        }
        q->_node = (void **)malloc(sizeof(void*)*q->_capacity);
        return q;
    }
    
    void tyl_queue_free(TYLQueue *q){
        int i;
        if (q) {
            for (i = 0; i < q->_size; i ++) {
                free(q->_node[i]);
            }
            free(q->_node);
            free(q);
        }
    }
    
    void tyl_queue_enqueue(TYLQueue *q,void *p){
        q->_node[q->_size] = p;
        q->_size++;
        if (q->_size >= q->_capacity) {
            tyl_queue_realloc(q);
        }
    }
    
    void *tyl_queue_dequeue(TYLQueue *q){
        void *p = NULL;
        if (q->_size > 0) {
            p = q->_node[0];
        }
        return p;
    }
    
    #pragma mark - internal method
    
    static void tyl_queue_realloc(TYLQueue *q){
        q->_capacity = q->_capacity*2;
        q->_node = realloc(q->_node, sizeof(void *) * q->_capacity);
    }
    
    fishhook.c
    #import "fishhook.h"
    
    #import <dlfcn.h>
    #import <stdlib.h>
    #import <string.h>
    #import <sys/types.h>
    #import <mach-o/dyld.h>
    #import <mach-o/loader.h>
    #import <mach-o/nlist.h>
    
    #ifdef __LP64__
    typedef struct mach_header_64 mach_header_t;
    typedef struct segment_command_64 segment_command_t;
    typedef struct section_64 section_t;
    typedef struct nlist_64 nlist_t;
    #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
    #else
    typedef struct mach_header mach_header_t;
    typedef struct segment_command segment_command_t;
    typedef struct section section_t;
    typedef struct nlist nlist_t;
    #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
    #endif
    
    #ifndef SEG_DATA_CONST
    #define SEG_DATA_CONST  "__DATA_CONST"
    #endif
    
    struct rebindings_entry {
      struct rebinding *rebindings;
      size_t rebindings_nel;
      struct rebindings_entry *next;
    };
    
    static struct rebindings_entry *_rebindings_head;
    
    static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                                  struct rebinding rebindings[],
                                  size_t nel) {
      struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry));
      if (!new_entry) {
        return -1;
      }
      new_entry->rebindings = malloc(sizeof(struct rebinding) * nel);
      if (!new_entry->rebindings) {
        free(new_entry);
        return -1;
      }
      memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
      new_entry->rebindings_nel = nel;
      new_entry->next = *rebindings_head;
      *rebindings_head = new_entry;
      return 0;
    }
    
    static bool str_longer(const char *symbol, size_t len) {
      for (size_t i = 0; i <= len; i++) {
        if (symbol[i] == 0) {
          return false;
        }
      }
      return true;
    }
    
    static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                               section_t *section,
                                               intptr_t slide,
                                               nlist_t *symtab,
                                               char *strtab,
                                               uint32_t *indirect_symtab) {
      uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
      void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
      for (uint i = 0; i < section->size / sizeof(void *); i++) {
        uint32_t symtab_index = indirect_symbol_indices[i];
        if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
            symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {
          continue;
        }
        uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
        char *symbol_name = strtab + strtab_offset;
        struct rebindings_entry *cur = rebindings;
        while (cur) {
          for (uint j = 0; j < cur->rebindings_nel; j++) {
            if (str_longer(symbol_name, 1) &&
                strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
              if (cur->rebindings[j].replaced != NULL &&
                  indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
                *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
              }
              indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
              goto symbol_loop;
            }
          }
          cur = cur->next;
        }
      symbol_loop:;
      }
    }
    
    static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                         const struct mach_header *header,
                                         intptr_t slide) {
      Dl_info info;
      if (dladdr(header, &info) == 0) {
        return;
      }
    
      segment_command_t *cur_seg_cmd;
      segment_command_t *linkedit_segment = NULL;
      struct symtab_command* symtab_cmd = NULL;
      struct dysymtab_command* dysymtab_cmd = NULL;
    
      uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
      for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
        cur_seg_cmd = (segment_command_t *)cur;
        if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
          if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
            linkedit_segment = cur_seg_cmd;
          }
        } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
          symtab_cmd = (struct symtab_command*)cur_seg_cmd;
        } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
          dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
        }
      }
    
      if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
          !dysymtab_cmd->nindirectsyms) {
        return;
      }
    
      // Find base symbol/string table addresses
      uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
      nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
      char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
    
      // Get indirect symbol table (array of uint32_t indices into symbol table)
      uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
    
      cur = (uintptr_t)header + sizeof(mach_header_t);
      for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
        cur_seg_cmd = (segment_command_t *)cur;
        if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
          if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
              strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
            continue;
          }
          for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
            section_t *sect =
              (section_t *)(cur + sizeof(segment_command_t)) + j;
            if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
              perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
            }
            if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
              perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
            }
          }
        }
      }
    }
    
    static void _rebind_symbols_for_image(const struct mach_header *header,
                                          intptr_t slide) {
        rebind_symbols_for_image(_rebindings_head, header, slide);
    }
    
    int rebind_symbols_image(void *header,
                             intptr_t slide,
                             struct rebinding rebindings[],
                             size_t rebindings_nel) {
        struct rebindings_entry *rebindings_head = NULL;
        int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
        rebind_symbols_for_image(rebindings_head, header, slide);
        free(rebindings_head);
        return retval;
    }
    
    int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
      int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
      if (retval < 0) {
        return retval;
      }
      // If this was the first call, register callback for image additions (which is also invoked for
      // existing images, otherwise, just run on existing images
      if (!_rebindings_head->next) {
        _dyld_register_func_for_add_image(_rebind_symbols_for_image);
      } else {
        uint32_t c = _dyld_image_count();
        for (uint32_t i = 0; i < c; i++) {
          _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
        }
      }
      return retval;
    }
    

    相关文章

      网友评论

          本文标题:iOS之异常处理及信号

          本文链接:https://www.haomeiwen.com/subject/adtgihtx.html