美文网首页工作中用到的好技术
iOS crash捕获:NSSetUncaughtExcepti

iOS crash捕获:NSSetUncaughtExcepti

作者: 东方诗空 | 来源:发表于2022-04-07 10:36 被阅读0次

    使用NSSetUncaughtExceptionHandler函数捕获

    #include <signal.h>
    #include <execinfo.h>
    
    void handleExceptions(NSException *exception) {
        
        NSLog(@"*****************************************************************");
        NSLog(@"exception 0000000000000 = %@",exception);
        
        NSLog(@"*****************************************************************");
    
        NSLog(@"callStackSymbols 11111111111111 = %@",[exception callStackSymbols]);
        NSLog(@"*****************************************************************");
    
    }
    
    void signalHandler(int sig) {
        //最好不要写,可能会打印太多内容
        NSLog(@"*****************************************************************");
    
        NSLog(@"signal 22222222222 =  %d", sig);
        NSLog(@"*****************************************************************");
    
    }
    
    - (void)initHandler {
        
        struct sigaction newSignalAction;
        memset(&newSignalAction, 0,sizeof(newSignalAction));
        newSignalAction.sa_handler = &signalHandler;
        sigaction(SIGABRT, &newSignalAction, NULL);
        sigaction(SIGILL, &newSignalAction, NULL);
        sigaction(SIGSEGV, &newSignalAction, NULL);
        sigaction(SIGFPE, &newSignalAction, NULL);
        sigaction(SIGBUS, &newSignalAction, NULL);
        sigaction(SIGPIPE, &newSignalAction, NULL);
        
        //异常时调用的函数
        NSSetUncaughtExceptionHandler(&handleExceptions);
    }
    
    
    • memset(&newSignalAction, 0,sizeof(newSignalAction));
      给newSignalAction 结构体设置初始值

    • sigaction(SIGABRT, &newSignalAction, NULL);
      sigaction(SIGILL, &newSignalAction, NULL);
      sigaction(SIGSEGV, &newSignalAction, NULL);
      sigaction(SIGFPE, &newSignalAction, NULL);
      sigaction(SIGBUS, &newSignalAction, NULL);
      sigaction(SIGPIPE, &newSignalAction, NULL);

    标记需要捕获的crash类型

    • 异常时调用的函数
      NSSetUncaughtExceptionHandler(&handleExceptions);

    NSSetUncaughtExceptionHandler 底层调用逻辑梳理

    NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler *handler)
    {
      _NSUncaughtExceptionHandler = handler;
    }
    
    
    • 传入的 handler 被NSException类对象持有

    • 接着callUncaughtHandler会调用_NSUncaughtExceptionHandler

    static void
    callUncaughtHandler(id value)
    {
      if (_NSUncaughtExceptionHandler != NULL)
        {
          (*_NSUncaughtExceptionHandler)(value);
        }
      _NSFoundationUncaughtExceptionHandler(value);
    }
    

    实现逻辑窥探

    且NSException 初始化时也会注册调用对应的方法

    + (void) initialize
    {
      if (self == [NSException class])
        {
    #if defined(_NATIVE_OBJC_EXCEPTIONS)
    #  ifdef HAVE_SET_UNCAUGHT_EXCEPTION_HANDLER
          objc_setUncaughtExceptionHandler(callUncaughtHandler);
    #  elif defined(HAVE_UNEXPECTED)
          _objc_unexpected_exception = callUncaughtHandler;
    #  elif defined(HAVE_SET_UNEXPECTED)
          objc_set_unexpected(callUncaughtHandler);
    #  endif
    #endif
        }
    }
    

    调用 [NSException raise: NSGenericException format: @"Terminate"];

      NSException *obj;
      NSMutableArray *testObjs = [[NSMutableArray alloc] init];
      NSAutoreleasePool   *arp = [NSAutoreleasePool new];
    
      test_alloc_only(@"NSException"); 
      obj = [NSException exceptionWithName: NSGenericException
                                    reason: nil
                                  userInfo: nil];
      PASS((obj != nil), "can create an exception");
      PASS(([[obj name] isEqualToString: NSGenericException]), "name works");
      obj = [NSException exceptionWithName: NSGenericException
                                    reason: nil
                                  userInfo: nil];
      [testObjs addObject: obj];
      test_NSObject(@"NSException", testObjs);
      
      NS_DURING
        [MyClass testAbc];
      NS_HANDLER
        {
          NSArray   *addresses = [localException callStackReturnAddresses];
          NSArray   *a = [localException callStackSymbols];
          NSEnumerator *e = [a objectEnumerator];
          NSString  *s = nil;
    
          PASS([addresses count] > 0, "call stack addresses is not empty");
          PASS([addresses count] == [a count], "addresses and symbols match");
    
    NSLog(@"Got %@", a);
          while ((s = [e nextObject]) != nil)
            if ([s rangeOfString: @"testAbc"].length > 0)
              break;
          testHopeful = YES;
          PASS(s != nil, "working callStackSymbols ... if this has failed it is probably due to a lack of support for objective-c method names (local symbols) in the backtrace_symbols() function of your libc. If so, you might lobby your operating system provider for a fix.");
          testHopeful = NO;
        }
      NS_ENDHANDLER
    
      PASS(NSGetUncaughtExceptionHandler() == 0, "default handler is null");
      NSSetUncaughtExceptionHandler(handler);
      PASS(NSGetUncaughtExceptionHandler() == handler, "setting handler works");
    
      fprintf(stderr, "We expect a single FAIL without any explanation as\n"
        "the test is terminated by an uncaught exception ...\n");
      [NSException raise: NSGenericException format: @"Terminate"];
      PASS(NO, "shouldn't get here ... exception should have terminated process");
    
      [arp release]; arp = nil;
    

    接着会来到调用:

    • (void) raise: (NSString)name
      format: (NSString
      )format,...

    • (void) raise: (NSString)name
      format: (NSString
      )format
      arguments: (va_list)argList

    + (void) raise: (NSString*)name
        format: (NSString*)format,...
    {
      va_list args;
    
      va_start(args, format);
      [self raise: name format: format arguments: args];
      // This probably doesn't matter, but va_end won't get called
      va_end(args);
      while (1);    // does not return
    }
    
    + (void) raise: (NSString*)name
        format: (NSString*)format
         arguments: (va_list)argList
    {
      NSString  *reason;
      NSException   *except;
    
      reason = [NSString stringWithFormat: format arguments: argList];
      except = [self exceptionWithName: name reason: reason userInfo: nil];
      [except raise];
      while (1);    // does not return
    }
    

    其中:[except raise]; 里面会调用 callUncaughtHandler(self);

    - (void) raise
    {
      if (_reserved == 0)
        {
          _reserved = NSZoneCalloc([self zone], 2, sizeof(id));
        }
      if (nil == _e_stack)
        {
          // Only set the stack when first raised
          _e_stack = [GSStackTrace new];
          [_e_stack trace];
        }
    
    #if     defined(_NATIVE_OBJC_EXCEPTIONS)
      @throw self;
    #else
    {
      NSThread      *thread;
      NSHandler *handler;
    
      thread = GSCurrentThread();
      handler = thread->_exception_handler;
      if (NULL == handler)
        {
          static    int recursion = 0;
    
          /*
           * Set/check a counter to prevent recursive uncaught exceptions.
           * Allow a little recursion in case we have different handlers
           * being tried.
           */
          if (recursion++ > 3)
        {
          fprintf(stderr,
            "recursion encountered handling uncaught exception\n");
          fflush(stderr);   /* NEEDED UNDER MINGW */
          _terminate();
        }
    
          /*
           * Call the uncaught exception handler (if there is one).
           * The calls the built-in default handler to terminate the program!
           */
          callUncaughtHandler(self);
        }
      else
        {
          thread->_exception_handler = handler->next;
          handler->exception = self;
          longjmp(handler->jumpState, 1);
        }
    }
    #endif
      while (1);    // does not return
    }
    
    

    最后:callUncaughtHandler 会调用会上层函数的回调监听: NSSetUncaughtExceptionHandler(&handleExceptions);

    那么问题来了:如何触发异常捕获调用呢?

    查看NSObject 对象的底层实现,会有一些不合法的判断,在不合法的地方调用

    + (void) raise: (NSString*)name
        format: (NSString*)format,...
    

    以数组NSArray为例:

     if (anObject == nil)
        [NSException raise: NSInvalidArgumentException
            format: @"Attempt to add nil to an array"];
    
    - (NSArray*) arrayByAddingObject: (id)anObject
    {
      id na;
      NSUInteger    c = [self count];
    
      if (anObject == nil)
        [NSException raise: NSInvalidArgumentException
            format: @"Attempt to add nil to an array"];
      if (c == 0)
        {
          na = [[GSArrayClass allocWithZone: NSDefaultMallocZone()]
        initWithObjects: &anObject count: 1];
        }
      else
        {
          GS_BEGINIDBUF(objects, c+1);
    
          [self getObjects: objects];
          objects[c] = anObject;
          na = [[GSArrayClass allocWithZone: NSDefaultMallocZone()]
        initWithObjects: objects count: c+1];
    
          GS_ENDIDBUF();
        }
      return AUTORELEASE(na);
    }
    
    

    符号表的调用

    获取符号表 由 GSStackTrace 类对象调用symbols 获得

    - (NSArray*) symbols
    {
      if (nil == symbols && numReturns > FrameOffset)
        {
          NSInteger         count = numReturns - FrameOffset;
          NSUInteger        i;
    
    #if defined(USE_BFD)
          void              **ptrs = (void**)&returns[FrameOffset];
          NSMutableArray    *a;
    
          a = [[NSMutableArray alloc] initWithCapacity: count];
    
          for (i = 0; i < count; i++)
            {
              GSFunctionInfo    *aFrame = nil;
              void              *address = (void*)*ptrs++;
              void              *base;
              NSString      *modulePath;
              GSBinaryFileInfo  *bfi;
    
              modulePath = GSPrivateBaseAddress(address, &base);
              if (modulePath != nil && (bfi = GSLoadModule(modulePath)) != nil)
                {
                  aFrame = [bfi functionForAddress: (void*)(address - base)];
                  if (aFrame == nil)
                    {
                      /* We know we have the right module but function lookup
                       * failed ... perhaps we need to use the absolute
                       * address rather than offest by 'base' in this case.
                       */
                      aFrame = [bfi functionForAddress: address];
                    }
                }
              else
                {
                  NSArray   *modules;
                  int   j;
                  int   m;
    
                  modules = GSListModules();
                  m = [modules count];
                  for (j = 0; j < m; j++)
                    {
                      bfi = [modules objectAtIndex: j];
    
                      if ((id)bfi != (id)[NSNull null])
                        {
                          aFrame = [bfi functionForAddress: address];
                          if (aFrame != nil)
                            {
                              break;
                            }
                        }
                    }
                }
    
              // not found (?!), add an 'unknown' function
              if (aFrame == nil)
                {
                  aFrame = [GSFunctionInfo alloc];
                  [aFrame initWithModule: nil
                                 address: address 
                                    file: nil
                                function: nil
                                    line: 0];
                  [aFrame autorelease];
                }
              [a addObject: [aFrame description]];
            }
          symbols = [a copy];
          [a release];
    #elif   defined(_WIN32)
          void              **ptrs = (void**)&returns[FrameOffset];
          SYMBOL_INFO   *symbol;
          NSString          *syms[MAXFRAMES];
    
          symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO)
            + 1024 * sizeof(char), 1);
          symbol->MaxNameLen = 1024;
          symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    
          (void)pthread_mutex_lock(&traceLock);
          for (i = 0; i < count; i++)
            {
              NSUInteger    addr = (NSUInteger)*ptrs++; 
    
              if ((fromSym)(hProcess, (DWORD64)addr, 0, symbol))
                {
                  syms[i] = [NSString stringWithFormat:
                    @"%s - %p", symbol->Name, addr];
                }
              else
                {
                  syms[i] = [NSString stringWithFormat:
                    @"unknown - %p", symbol->Name, addr];
                }
            }
          (void)pthread_mutex_unlock(&traceLock);
          free(symbol);
    
          symbols = [[NSArray alloc] initWithObjects: syms count: count];
    #elif   defined(HAVE_BACKTRACE)
          void              **ptrs = (void**)&returns[FrameOffset];
          char      **strs;
          NSString          **symbolArray;
    
          strs = backtrace_symbols(ptrs, count);
          symbolArray = alloca(count * sizeof(NSString*));
          for (i = 0; i < count; i++)
            {
              symbolArray[i] = [NSString stringWithUTF8String: strs[i]];
            }
          symbols = [[NSArray alloc] initWithObjects: symbolArray count: count];
          free(strs);
    #elif defined(WITH_UNWIND)
          void              **ptrs = (void**)&returns[FrameOffset];
          NSString          **symbolArray;
    
          symbolArray = alloca(count * sizeof(NSString*));
          for (i = 0; i < count; i++)
            {
              const void *addr = ptrs[i];
              Dl_info info;
              if (dladdr(addr, &info)) {
                const char *libname = "unknown";
                if (info.dli_fname) {
                  // strip library path
                  char *delim = strrchr(info.dli_fname, '/');
                  libname = delim ? delim + 1 : info.dli_fname;
                }
                if (info.dli_sname) {
                  symbolArray[i] = [NSString stringWithFormat:
                    @"%lu: %p %s %s + %d", (unsigned long)i, addr, libname,
                    info.dli_sname, (int)(addr - info.dli_saddr)];
                } else {
                  symbolArray[i] = [NSString stringWithFormat:
                    @"%lu: %p %s unknown", (unsigned long)i, addr, libname];
                }
              } else {
                symbolArray[i] = [NSString stringWithFormat:
                  @"%lu: %p unknown", (unsigned long)i, addr];
              }
            }
          symbols = [[NSArray alloc] initWithObjects: symbolArray count: count];
    #else
          NSMutableArray    *a;
    
          symbols = a = [[self addresses] mutableCopy];
          for (i = 0; i < count; i++)
            {
              NSString      *s;
    
              s = [[NSString alloc] initWithFormat: @"%p: symbol not available",
                [[a objectAtIndex: i] pointerValue]];
              [a replaceObjectAtIndex: i withObject: s];
              RELEASE(s);
            }
    #endif
        }
      return symbols;
    }
    
    

    相关文章

      网友评论

        本文标题:iOS crash捕获:NSSetUncaughtExcepti

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