草庐IT

iOS crash捕获:NSSetUncaughtExceptionHandler

东方诗空 2023-03-28 原文

使用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捕获:NSSetUncaughtExceptionHandler的更多相关文章

  1. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  2. ruby-on-rails - 无法在 Rails 助手中捕获 block 的输出 - 2

    我在使用自定义RailsFormBuilder时遇到了问题,从昨天晚上开始我就发疯了。基本上我想对我的构建器方法之一有一个可选block,以便我可以在我的主要content_tag中显示其他内容。:defform_field(method,&block)content_tag(:div,class:'field')doconcatlabel(method,"Label#{method}")concattext_field(method)capture(&block)ifblock_given?endend当我在我的一个Slim模板中调用该方法时,如下所示:=f.form_field:e

  3. ruby - 在 ruby​​ 中生成一个进程,捕获 stdout,stderr,获取退出状态 - 2

    我想从ruby​​rake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调

  4. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

  5. ruby - Capistrano 中的执行、测试和捕获命令有什么区别? - 2

    关于SSHkit-Github它说:Allbackendssupporttheexecute(*args),test(*args)&capture(*args)来自SSHkit-Rubydoc,我明白execute实际上是test的别名?test之间有什么区别?,execute,capture在Capistrano/SSHKit中我应该什么时候使用? 最佳答案 执行只是执行命令。使用非0退出引发错误。测试方法的行为与execute完全相同,但是它返回bool值(true如果命令以0退出,而false否则)。它通常用于控制任务中的流程

  6. ruby - 如何捕获 ruby​​ 中的所有异常? - 2

    我们如何捕获或/和处理ruby​​中所有未处理的异常?例如,这样做的动机可能是将某种异常记录到不同的文件或发送电子邮件给系统管理。在Java中我们会做Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandlerex);在Node.js中process.on('uncaughtException',function(error){/*code*/});在PHP中register_shutdown_function('errorHandler');functionerrorHandler(){$error=error_

  7. ruby - 正则表达式 - 保存重复捕获的组 - 2

    这就是我做的a="%span.rockets#diamonds.ribbons.forever"a=a.match(/(^\%\w+)([\.|\#]\w+)+/)putsa.inspect这是我得到的#这就是我想要的#帮助?我尝试过但失败了:( 最佳答案 通常,您不能获得任意数量的捕获组,但如果您使用扫描,您可以为您想要捕获的每个标记获得一个匹配:a="%span.rockets#diamonds.ribbons.forever"a=a.scan(/^%\w+|\G[.|#]\w+/)putsa.inspect["%span","

  8. ruby - 如何捕获所有 HTTP 流量(本地代理) - 2

    我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)

  9. ruby-on-rails - Ruby 从 bash 脚本执行中捕获 stderr 输出 - 2

    我目前可以将stdout重定向到ruby​​/rails中的字符串变量,只需在bash中运行命令并将结果设置为我的字符串变量,如下所示。val=%x[#{cmd}]其中cmd是表示bash命令的字符串。但是,这仅捕获stdout,因为我想捕获stderr并将其设置为ruby​​中的字符串——有什么想法吗? 最佳答案 简单地重定向它:val=%x[#{cmd}2>&1]如果您只想从stderr捕获输出,请在将其复制到fd2后关闭stdout的文件描述符。val=%x[#{cmd}2>&1>/dev/null]

  10. ruby - 正则表达式在 Ruby 中捕获相同数字的组 - 2

    是否可以在Ruby上使用正则表达式捕获字符串中所有相同数字的grous?我不熟悉正则表达式。我的意思是:"1112234444"上的正则表达式将生成["111","22","3","4444"]我知道,我可以使用(\d)(\1*),但它在每场比赛中只给我2个组。["1","11"],["2","2"],["3",-],["4","444"]如何在每场比赛中获得一组?谢谢。 最佳答案 在这里,试一试:((\d)\2*) 关于ruby-正则表达式在Ruby中捕获相同数字的组,我们在Stack

随机推荐