工作中用到的好技术

iOS crash捕获:NSSetUncaughtExcepti

2022-04-07  本文已影响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);
}

标记需要捕获的crash类型

NSSetUncaughtExceptionHandler 底层调用逻辑梳理

NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler *handler)
{
  _NSUncaughtExceptionHandler = handler;
}

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,...
{
  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;
}

上一篇 下一篇

猜你喜欢

热点阅读