Lazy loaded image
Frida学习笔记(七):调用栈、日志与 Hook 排错
Words 8007Read Time 21 min
2026-7-8
2026-7-8
type
Post
status
Published
date
Jul 8, 2026
slug
summary
本篇目标:当 Hook "不灵" 的时候,你能系统地排查出原因。前六篇给了你完整的武器库(Java Hook、Native Hook、主动调用、内存操作),但在实战中你会频繁遇到各种问题——类找不到、Hook 了但没输出、App 崩溃、日志被淹没……这些问题不是偶尔碰运气能解决的,你需要一套可重复的诊断方法论。本篇就是这个方法论。
tags
frida
Android
逆向工程
category
公众号
icon
password
网址
上次编辑时间
Jul 8, 2026 03:53 AM
comment
AI 总结
💡
本篇目标:当 Hook "不灵" 的时候,你能系统地排查出原因。前六篇给了你完整的武器库(Java Hook、Native Hook、主动调用、内存操作),但在实战中你会频繁遇到各种问题——类找不到、Hook 了但没输出、App 崩溃、日志被淹没……这些问题不是偶尔碰运气能解决的,你需要一套可重复的诊断方法论。本篇就是这个方法论。

一、为什么需要一篇专门讲排错的文章

前面六篇的代码示例都很顺畅——给定类名、方法名、Hook、拿到结果。但真实的逆向场景常常没这么顺:
  • 你从 jadx 复制的类名在 Frida 中报 ClassNotFoundException
  • Hook 成功了(没有报错),但操作 App 后控制台一条日志都没有
  • 加载脚本的瞬间 App 闪退
  • 控制台被洪水般的日志淹没,有用的信息被冲掉
  • Native Hook 后 App 行为完全异常
  • 同一个脚本昨天能用今天不能用了
这些不是 Frida 的 bug,而是你对目标 App 的理解和 Frida 的行为之间存在"信息差"。本篇的目标是帮你系统化地缩小这个信息差

二、日志管理:console.log、send() 与文件写入

在开始讲排错之前,先讲好日志基础设施——因为排错的第一步就是"看日志"。如果日志系统本身就有问题(丢日志、太多、格式乱),排错将寸步难行。

2.1 console.log:最基本的输出

console.log 的输出会通过 Frida 的通信通道从目标进程发送到你的电脑终端上显示。这个过程是异步的——你的 JS 代码不会等 console.log 的内容显示到屏幕上才继续执行。
性能影响:每次 console.log 都涉及一次 IPC(进程间通信)。在高频 Hook 场景(如 Hook Socket.write,每秒可能触发几百次)中,过多的 console.log 会显著拖慢 App,甚至导致 Frida 通信管道拥堵——表现为日志输出严重延迟、App 卡顿、甚至 Frida 连接断开。

2.2 send():结构化消息通道

send() 是 Frida 提供的另一个通信机制。和 console.log(纯文本输出)不同,send() 可以发送结构化的 JSON 数据,并且支持附带二进制数据。
Python 端接收:
send() vs console.log 的选择
场景
推荐
原因
临时调试、快速查看
console.log
简单直接
需要在 Python 端处理数据
send()
结构化 JSON + 二进制
需要传输二进制数据(dump 内存、密文等)
send()
支持 ArrayBuffer 附件
自动化工具、RPC 场景
send()
Python 端可以编程处理
需要日志分级和过滤
自建日志函数
见下节

2.3 构建日志分级系统

在复杂的 Hook 脚本中,不同模块产生的日志混在一起很难阅读。建议用一个简单的日志工具:

2.4 将日志写入文件

当日志量很大时,控制台输出会丢失(Frida 的通信缓冲区有限)。可以通过 Java API 把日志写到 App 目录下的文件中:
pull 日志adb shell run-as com.example.app cat files/frida_log.txt > local_log.txt,或者 adb pull /data/data/com.example.app/files/frida_log.txt(需要 root)。

2.5 控制日志洪水

高频 Hook(如 MessageDigest.updateSocket.write)可能每秒触发几百次。不加控制地打印日志会导致:
  1. Frida 通信管道拥堵 → 日志严重延迟
  1. App 运行速度下降数十倍
  1. 有用信息被海量重复日志淹没
控制策略:

三、frida-trace:快速侦察的利器

在写详细的 Hook 脚本之前,frida-trace 可以帮你快速回答"这个方法到底有没有被调用"——10 秒命令就能回答的问题,没必要先写脚本。

3.1 追踪 Java 方法

-j 参数的格式是 类名模式!方法名模式* 是通配符。
运行后,每当 App 调用匹配的方法时,终端会实时显示调用记录(含参数和返回值)。这让你在不写任何脚本的情况下就能观察方法的调用情况。

3.2 追踪 Native 函数

-i / -x 分别按函数名包含 / 排除;-I / -X 分别按模块名包含 / 排除(-I 是"包含整个模块",不是排除)。

3.3 自定义追踪处理器

frida-trace 首次追踪一个函数时,会在当前目录下生成一个 __handlers__/ 目录,里面包含每个被追踪函数的处理器脚本。你可以编辑这些脚本来自定义行为:
编辑保存后,下次运行 frida-trace 会自动使用修改后的处理器。

3.4 frida-trace 的适用场景

场景
是否适合用 frida-trace
快速确认某个方法是否被调用
非常适合
了解 App 启动时调用了哪些加密方法
非常适合
观察 Native 函数的调用顺序
适合
需要修改参数或返回值
不太适合(用自定义脚本更方便)
需要复杂的条件过滤和数据处理
不太适合
需要 RPC 调用和自动化
不适合
典型工作流:先用 frida-trace 快速扫描,确认目标方法在哪里、被谁调用、多频繁调用——然后再写针对性的 Hook 脚本做深入分析。

四、Hook 排错系统化流程

当 Hook 不符合预期时,按以下流程系统化排查。每一步都是在缩小问题范围。
notion image
Hook 排错决策树

4.1 第一步:确认 Frida 连接正常

在排查 Hook 问题之前,先确保 Frida 本身工作正常:
如果第三步能看到 Frida OK 输出,说明 Frida 连接正常。如果不能,回到第02篇排查环境问题。

4.2 第二步:确认脚本是否有语法错误

JavaScript 语法错误会导致整个脚本不执行,但 Frida 的错误提示有时不够清晰。
如果只看到 脚本开始加载 但没看到 Java.perform 开始执行,说明 Java.perform 之前就出错了。如果两条都没看到,说明脚本压根没被加载(检查文件路径和语法)。

4.3 第三步:Hook 设置是否成功

Hook 代码可能执行了但 Hook 没有实际生效——因为类找不到、方法签名错误等。关键是要**区分"设置阶段的失败"和"运行时的不触发"**。

4.4 第四步:方法是否被 App 调用了

如果 Hook 设置成功(没有报错),但操作 App 后没有看到 [HIT] 日志——问题可能不在 Frida,而在 App。
可能性一:你操作的功能没有触发目标方法。 比如你 Hook 了 LoginManager.login,但在 App 中点击的是"注册"按钮——注册流程调用的是 RegisterManager.register,根本不走 login
frida-trace 确认:
可能性二:App 使用了不同的代码路径。 混淆后的 App 可能有多个看起来相似的方法,jadx 中你看到的 a.b.c.d() 可能不是实际被调用的那个 d()
可能性三:方法确实被调用了,但在另一个进程中。 很多 App 使用多进程架构(加密操作在 :crypto 进程中,网络操作在 :network 进程中)。见 §4.5 详细讲解。

4.5 第五步:检查是否是多进程问题

Android App 可以在 AndroidManifest.xml 中为不同组件指定不同的进程名(android:process 属性)。例如:
当你用 frida -U -f com.example.app 注入时,Frida 只注入主进程。如果目标方法在子进程中运行,你的 Hook 自然不会触发。
诊断方法
如果有多个进程,尝试分别注入:
-n 只能附加已运行的子进程。如果加密操作在 App 启动早期就执行(典型场景:加固壳在子进程中解密),子进程往往在你来得及附加前就跑完了关键逻辑。这种情况需要 child gating——让 Frida 在 fork 出子进程时立即挂起,待脚本就位再 resume:
notion image

五、打印调用栈:定位"谁调用了我"

很多排错场景里,问题不在"方法有没有被 Hook 到",而在"这个方法是从哪条代码路径来的"。比如:你 Hook 到了 Cipher.doFinal,但 App 里有 30 个地方调用它,你想知道当前这次调用是登录流程还是消息加密。打印调用栈是回答这类问题最直接的工具。

5.1 Java 层调用栈

Java 层有两种打印调用栈的方式,效果接近,写法不同:
两种方式都会打印形如 at com.example.app.LoginManager.encryptPassword(LoginManager.java:42) 的调用栈帧。实战中优先用方式一——Log.getStackTraceString 自带格式化、输出更紧凑;需要按帧过滤时再用方式二。

5.2 Native 层调用栈

Native Hook 中用 Thread.backtrace 取栈,配合 DebugSymbol.fromAddress 解析符号:
输出形如:
两种 Backtracer 模式
模式
速度
精度
适用
Backtracer.ACCURATE
高(依赖栈展开元数据)
默认选择
Backtracer.FUZZY
低(启发式扫描栈)
ACCURATE 取不到栈帧时退而求其次
如果调用来自 Java 层(典型情况:JNI 函数),Native 栈底部会出现 art_quick_invoke_stub 这类 ART 内部符号——这是 Java→Native 的边界,再往下要切到 Java 栈才看得清。最完整的做法是 Java 栈 + Native 栈同时打印:在 JNI Hook 里既调 Thread.backtrace 也借 Java API 取一次 Java 栈。
notion image
Java 栈与 Native 栈在 JNI 边界拼接

5.3 调用栈裁剪与过滤

完整调用栈往往几十帧,多数是 Framework 或运行时的"水帧"。过滤掉无信息帧能让输出可读得多:

5.4 什么时候打印调用栈

打印调用栈对 IPC 通信和 App 性能有可观开销——一次完整 Java 栈格式化大约几百字节、几毫秒。建议:
  • 侦察阶段打:首次找到目标方法被调用时,打一次完整栈,建立"调用路径地图"。
  • 复现阶段不打:调用路径明确后,去掉栈打印只留参数日志。
  • 条件触发打:用 §2.5 的条件过滤策略——只在特定参数值或第 N 次调用时打印,避免日志洪水。

六、常见错误完整排查手册

6.1 ClassNotFoundException

这是 Frida Java Hook 的头号错误。 第03篇和第05篇都涉及了部分原因,这里给出完整的排查清单。
排查步骤(按可能性从高到低)
Step 1:检查类名拼写。
Step 2:确认类是否已加载。
如果搜不到——类可能确实还没被加载(App 还没执行到加载它的代码)。等 App 充分运行后再试。
Step 3:检查是否是 ClassLoader 问题。
Step 4:App 是否有加固?
加固 App 的业务类被加密存储在 DEX 中,只有壳解密后才能加载。如果你在 App 启动早期就尝试 Java.use,类确实不存在。
诊断方法:
辅助判定:除 Application 类名外,还可看 lib/<abi>/ 下是否有特征 SO(如 libDexHelper.solibsecexe.solibBugly_*.solibshellx.so 等),以及 assets/ 下是否有未知的加密 dex 文件。
对于加固 App,需要等壳解密完成后再 Hook。通常的做法是 Hook Application.onCreateClassLoader.loadClass,在类被加载时再执行你的 Hook 逻辑:

6.2 Hook 设置成功但不触发

这是第二常见的问题——没有报错,但 implementation 回调从未被调用。
notion image
Hook 不触发决策树
排查清单
可能原因
诊断方法
解决方案
操作没有触发目标方法
frida-trace 确认
确认正确的操作路径
多进程,方法在子进程中
frida-ps -U | grep 包名
注入正确的进程
方法被 ProGuard 混淆
jadx 搜索原始名 → 找混淆后的名字
用混淆后的类名/方法名
方法有多个重载,Hook 了错误的版本
枚举所有重载
逐个尝试或用 overloads 全部 Hook
JIT 编译导致 Hook 被绕过
Java.deoptimizeEverything()
关闭 JIT
Hook 时机过晚,方法只在启动时调用一次
Spawn 模式启动
frida -U -f 包名
方法确实没有被调用(死代码)
jadx 中查看调用关系
换一个 Hook 点
用 overloads 一次性 Hook 所有重载版本(最强壮的方式):

6.3 Hook 后 App 崩溃

崩溃类型一:加载脚本的瞬间就崩溃。
通常是 App 检测到了 Frida 并主动退出。诊断:
崩溃类型二:操作 App 触发 Hook 时崩溃。
常见原因:
崩溃类型三:Native Hook 后崩溃。
诊断 Native 崩溃
崩溃日志中的 signal 11 (SIGSEGV) 表示段错误(非法内存访问),signal 6 (SIGABRT) 表示主动退出(通常是检测到异常后 abort()),signal 5 (SIGTRAP) 可能是反调试检测触发的断点。

6.4 Error: expected a pointer(类型错误)

这个错误通常出现在 Interceptor.attach 的第一个参数不是一个有效的 NativePointer。

6.5 Unable to find method(方法找不到)

原因一:方法名拼写错误或方法不存在。
原因二:overload 参数类型写错。
Frida overload 中常见 Java 类型的写法
Java 类型
overload 中的写法
int
"int"
long
"long"
boolean
"boolean"
byte
"byte"
float
"float"
double
"double"
String
"java.lang.String"
Object
"java.lang.Object"
Context
"android.content.Context"
int[]
"[I"
byte[]
"[B"
long[]
"[J"
String[]
"[Ljava.lang.String;"
Object[]
"[Ljava.lang.Object;"
List
"java.util.List"
Map
"java.util.Map"
数组类型的 JVM 编码规则[ 表示一维数组,I = int, B = byte, J = long, F = float, D = double, Z = boolean, C = char, S = short。对象数组格式为 [L全限定类名;(注意末尾的分号)。二维数组如 int[][]"[[I"

七、高级排错技巧

7.1 延迟 Hook:等待类加载

有些类只在 App 运行到特定页面或功能时才加载。你可以用"延迟 Hook"模式——先等待目标类出现,再设置 Hook:
另一种更优雅的方式——监听类加载事件:

7.2 多 Hook 冲突排查

当你同时加载多个脚本(或一个脚本中对同一个方法 Hook 了两次),可能出现奇怪的行为——某些 Hook 不触发,或者触发顺序异常。
原则:一个 Java 方法同一时刻只能有一个 implementation 替换。后设置的 Hook 会覆盖先设置的。

7.3 logcat 配合 Frida 排错

Android 的 logcat 是与 Frida 互补的信息源。App 自身的日志(Log.dLog.e)、系统日志、崩溃信息都在 logcat 中。
在 Frida 中也可以 Hook android.util.Log 来捕获 App 的日志输出:

八、排错速查清单

遇到问题时,按这张清单逐项检查:

总结

本篇建立了一套系统化的 Frida 排错方法论。核心要点:
日志管理是排错的基础设施。console.log 适合快速调试,send() 适合结构化数据传输和自动化场景。在高频 Hook 中必须控制日志量——采样、条件过滤、限速是三种基本策略。当日志量太大时,写入文件比打印到控制台更可靠。
frida-trace 是快速侦察的利器。在写详细脚本之前,先用 frida-trace -j '类名!方法名' 确认方法是否被调用、什么时候被调用、参数长什么样。它能在 10 秒内回答你可能要花 10 分钟写脚本才能回答的问题。
排错的核心思路是逐步缩小范围:Frida 连接 → 脚本加载 → Hook 设置 → 方法触发 → 输出正常。每一步都有明确的诊断方法,不要跳步猜测。
五个最常见的问题及其快速解法:
  1. → 检查拼写、ClassLoader 切换、等待壳解密
  1. Hook 不触发 → frida-trace 确认、全重载 Hook、检查多进程、JIT 关闭
  1. Hook 后崩溃 → 检查返回值、确保调用原始方法、检查 logcat
  1. 方法名找不到 → 枚举所有方法、注意混淆后的名字
  1. Native Hook expected a pointer → 检查函数地址是否为 null、SO 是否加载
上一篇
爬虫数据采集加密算法全景指南
下一篇
Frida学习笔记(六):Native 层 API 全解

Comments
Loading...