利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题

简介:

昨天遇到一个仅仅有一行错误信息的问题:

-[NSNull objectForKey:]: unrecognized selector sent to instance 0x537e068

因为这个问题发生在次线程。所以没有太实用的堆栈信息。而是仅仅有简单的SIGABRT信息:



考虑到unrecognized selector sent to instance这类问题是因为向某个对象发送了未实现的消息,这个过程大致例如以下(图片摘自这里):


參考Objective-C的对象模型:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

消息发送的流程大致例如以下:

  • 推断发送的消息是否为retain等内存管理方法。
  • 推断receiver是否为nil;
  • 推断是否在方法缓存中,即struct objc_cache *cache;
  • 推断是否在方法列表中,即struct objc_method_list **methodLists,因为对象的方法能够动态加入,所以这里的类型是struct objc_method_list **,能够參考objc-class.m源文件。
  • 推断是否在继承体系中——到这里,称之为Messaging过程。
  • 假设实在找不到。就运行Dynamic Method Resolution过程,即尝试调用resolveInstanceMethod:或resolveClassMethod:方法,我们能够通过实现这两个方法来动态加入方法;
  • 动态方法解析过程假设返回NO,那么还有最后的解救机会,就是Message Forwarding消息转发过程(參考NSObject.h):

- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

我第一反应是加入resolveInstanceMethod:来观察,这是一个类方法。所以得加入到metaClass上:

Class metaClass = objc_getMetaClass("NSNull");
SEL sel = @selector(resolveInstanceMethod:);
const char *type = "c@::";
class_addMethod(metaClass, sel, (IMP)resolveInstanceMethod, type);

但遗憾的是,即便此时方法寻找不到时会调用到resolveInstanceMethod:方法。只是在设置的断点位置看也已经没有明白的堆栈信息了。所以我就直接加入找不到的方法objectForKey:来定位:

Class metaClass = objc_getMetaClass("NSNull");
SEL sel = @selector(objectForKey:);
const char *type = "@@:@";
class_addMethod(metaClass, sel, (IMP)objectForKey, type);

样一来。通过在我们加入的objectForKey方法中设置断点就能够获取到具体堆栈信息,从而进一步定位到问题所在:
{
  fromId = "\U6d4b\U8bd520#\U65fa\U4f01\U65e0\U7ebf\U6d4b\U8bd5";
  msgContent = "<null>";
  msgSendTime = 1402909302;
  msgType = 12;
  uuid = 0;
}

原因是因为服务端推送的消息中一个必填字段为空。而client也刚好在此处没有使用项目代码中约定的类型检查宏(此处应为VFDict),而是直接当做NSDictionary来操作。






本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5084719.html,如需转载请自行联系原作者


相关文章
|
监控 安全 Java
Java多线程调试技巧:如何定位和解决线程安全问题
【4月更文挑战第6天】本文探讨了Java并发编程中的线程安全问题,包括数据不一致、死锁和性能下降。为解决这些问题,文章介绍了理解线程安全的重要性,如互斥、同步和避免死锁,并提供了识别问题的迹象和调试工具,如JConsole、VisualVM、堆栈跟踪和Thread Dump分析。此外,还建议使用原子类、线程安全数据结构和静态代码分析工具来加强同步和减少锁粒度。最后,强调了避免共享状态和合理设计的重要性,以确保多线程程序的正确性和效率。
672 2
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
390 0
|
监控 安全 Java
Java多线程调试技巧:如何定位和解决线程安全问题
Java多线程调试技巧:如何定位和解决线程安全问题
394 2
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
1658 2
|
NoSQL
线程死循环的定位方法
线程死循环的定位方法
368 2
|
监控 安全
线程死循环是多线程应用程序开发过程中一个难以忽视的问题,它源于线程在执行过程中因逻辑错误或不可预见的竞争状态而陷入永久运行的状态,严重影响系统的稳定性和资源利用率。那么,如何精准定位并妥善处理线程死循环现象,并在编码阶段就规避潜在风险呢?谈谈你的看法~
避免线程死循环的关键策略包括使用同步机制(如锁和信号量)、减少共享可变状态、设置超时、利用监控工具、定期代码审查和测试、异常处理及设计简洁线程逻辑。通过这些方法,可降低竞态条件、死锁风险,提升程序稳定性和可靠性。
379 0
|
监控 安全 Java
线程死循环定位与处理:精准定位,妥善处理,预防为先
精准定位死循环,妥善处理问题,编码阶段防风险。
627 0
|
11月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
483 83
|
8月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
320 6
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
443 0

热门文章

最新文章