您当前的位置:首页 > 互联网教程

自定义 Hook,解决 RxJava 异常时堆栈信息显示不全

发布时间:2025-05-21 21:54:35    发布人:远客网络

自定义 Hook,解决 RxJava 异常时堆栈信息显示不全

一、自定义 Hook,解决 RxJava 异常时堆栈信息显示不全

1、在软件开发过程中,RxJava异常捕获时堆栈信息不全的问题曾困扰着不少开发者。典型场景中,捕获到的异常信息只包括系统或框架的代码,而无法追踪到实际引发错误的调用路径。本文旨在探讨如何通过自定义Hook解决这一问题,并介绍一种基于Hook机制的解决方案。

2、堆栈信息在程序中扮演着关键角色,用于追踪错误发生的上下文和调用路径。在Java中,通过`java.lang.Thread#getStackTrace`方法可获取当前线程的堆栈信息。然而,RxJava在抛出异常时提供的堆栈信息通常仅涉及`Callable#call`方法的调用栈,并不包含`Callable`创建时的堆栈信息,这导致了调用路径的缺失。

3、为了解决这一问题,一种方法是自定义Hook机制,捕获`Observable#fromCallable`方法的调用并记录其堆栈信息。通过使用如wenshu大神的Hook框架或自定义实现,可以在`Observable#fromCallable`方法执行前后添加拦截逻辑,确保捕获到`Callable`创建时的堆栈信息。这使得开发者在面对异常时,能够获得更完整的调用路径信息,从而更高效地定位和解决错误。

4、另一种方案则是利用第三方库RxJavaExtensions。该库提供了一种简便的方式来增强异常处理能力,确保异常信息中包含调用路径的上下文信息。通过简单地引入依赖库、启用错误追踪、并打印堆栈信息,开发者可以轻松地获取更详细的错误信息,从而提升调试效率。

5、上述解决方案的核心在于,通过Hook机制或特定库的实现,在`Callable`创建时捕获其堆栈信息,并在异常发生时将这些信息与错误信息结合,形成完整的调用路径。这种方式既利用了装饰者模式的灵活性,又简化了错误追踪的过程。

6、考虑到在生产环境中直接使用此类Hook机制可能带来的性能开销,开发者还需要权衡利弊。如果项目已经接入类似Matrix的性能分析工具,可以考虑结合使用Matrix的trace功能,以更高效的方式收集调用栈信息,同时利用插桩技术获取方法执行的耗时和上下文信息。这样既能够满足调试需求,又能够优化性能监控的实现。

二、hook是什么意思

作为单词时其意思是挂钩,吊钩或者是钩住的意思;作为音乐的意思是是一种音乐的表现形式,通常出现在副歌的位置。用在篮球领域的指的是一个篮球动作。同时还是力学弹性理论中的一条基本定律以及Windows系统机制的意思。

作为单词时其既可以做名词使用,也可以做动词使用。如用作名词例句Please hang your coat on the hook.(请把你的外衣挂在钩上)。用作动词例句Please hook the rope over the nail.(请把绳子挂在那根钉子上)。

1、hook单词用法:hook用作名词时意思是“钩”,转化为动词时可表示把某物弯成钩形,也可表示用弯曲的东西把某物体钩住,引申可表示为“吊”“挂”等。

2、hook单词的近义词:catch赶上;hanger挂钩;fasten拴紧;snare陷阱;clasp扣子;trap圈套;button纽扣;nail钉子;bind捆绑。

3、hook常用短语:用作动词时hook up(装好,扣住);用作名词by hook or by crook(千方百计)。

4、hook单词短语搭配:Cargo hook(货钩;吊货钩;吊钩);crochet hook(钩针;钩编钩针)。

5、hook单词过去式是hooked;过去分词是hooked;现在分词是hooking;第三人称单数是hooks。

三、从inlinehook角度检测frida

1、在使用frida时,我了解到它主要执行两项任务:注入和hook。关于注入,我们可以通过ptrace(PTRACE_TRACEME, NULL, 0, 0)或查找包含frida字符串的文件来检测。但关于hook,能否检测frida呢?答案是肯定的。因为frida通过跳转来修改内存中的代码,必然使用了inlinehook技术。

2、frida的inline hook写法如下,我们可以使用ida查看。例如,我hook了art::ClassLinker::LoadMethod函数,需要注意先附加frida再使用ida链接。否则,frida无法附加已经被ptrace附加的进程。结果如下图所示:

3、可以看到,frida的hook原理是将函数开头改为特定16进制字符串,如0xd61f020058000050,与我之前写的inlinehook类似,只是它使用了x16寄存器,我使用了x17寄存器。那么,我们能否通过检测so中是否存在这一段代码来判断用户是否使用了frida呢?答案是肯定的,但需要稍作调整,即在函数开头检测是否有这一段代码,并创建线程。

4、下面开始实现,首先尝试检测单个函数。我使用了上篇文章提到的findsym方法来提取符号首地址。可以看到,当我以attach方式链接上去时,成功检测到了frida。那么,我们可以遍历符号表来获得so中所有函数的首地址,从而检测是否使用了frida。由于某些函数不在导出表中,而在符号表中,所以可能无法通过程序头获取。这里以libart.so和libnative-lib.so为例。

5、首先封装了两个函数:一个是获得所有符号首地址,另一个是获得大小,都是通过节头表索引得到的。其中enumsym函数使用了两个int,第一个size是最大值,第二个start1是最小值,通过外部传入。然后,将这两个函数调用写入线程函数,我这里直接使用了0x25000,通过libart.so的程序头可以解析出来,每10秒检测一次。

6、结果不错,虽然检测速度稍慢,但达到了预定的目标。接着,我们继续检测libnative-lib。由于目录特殊性,我们很难找到它的目录,所以只能用程序头搜索其导出函数。这里直接贴代码,并注意确定符号表大小的方法。使用frida脚本hookJava_com_r0ysue_antifrida_MainActivity_stringFromJNI,查看效果。

7、上篇文章介绍了Java hook的做法,即把Java函数转换为Native函数,我们可以在关键函数上检测是否是Native方法来检测frida。当然,也可以通过解析dex来获取所有类表和函数名。这里先不展开,有机会下篇文章再讲。这里只提供一个最简单的方式,即通过Method来获得ArtMethod的方法检测是否是Native函数。具体做法可以参考上一篇文章,这里直接贴代码。使用反射,但注意由于线程不同,env也不同,不能将主线程的env传入检测线程使用。

8、以attach方式附加检测效果如下。

9、这里提供的frida检测方法比较简单,当然可以添加各种混淆和自实现线程创建函数,或者fork子进程。今天的内容就到这里,感谢大家观看。