前言
这是一个学习性质的项目,研究如何通过 Frida 的 CModule 实现高性能指令追踪。虽然 Frida 官方已经有了 frida-itrace 这个成熟方案,但直接用 CModule 手写可以更深入理解 Stalker 的工作原理和性能优化思路。
项目地址:https://github.com/vwww-droid/c-sktrace
解决了什么问题
原始的 JavaScript 实现在进行密集指令追踪时存在严重的卡顿问题,主要体现在:
通过 C 实现后,性能提升约两三个数量级(没细测,不过提升明显),实现了无卡顿的实时追踪。
性能瓶颈分析
JS 版本执行流程
function stalkerTraceRange(tid, base, size) {
Stalker.follow(tid, {
transform: (iterator) => {
do {
iterator.keep();
if (isModuleCode) {
// ❌ 性能问题:每条指令都注册 JS 回调
iterator.putCallout((context) => {
send({...}) // Native → JS 切换
})
}
} while (iterator.next() !== null);
}
})
}
Runtime 阶段开销
目标代码执行到被 trace 的指令
↓
Callout 触发:Native 代码暂停
↓ 切换到 JS 引擎
执行 JS 回调函数
↓ JSON 序列化 context
调用 send()(进程间通信)
↓ 切回 Native
恢复目标代码执行
示例:追踪一个执行 10,000 次的循环
引擎切换的开销
每次切换需要:
[ol]
[/ol]
还是很大的性能消耗
解决方案:CModule + Python 异步处理
核心思路
既然无法消除所有 JS 交互,那就:
[ol]
[/ol]
优势
实现细节
1. Transform 函数
void transform(GumStalkerIterator *iterator,
GumStalkerOutput *output,
gpointer user_data)
{
cs_insn *insn;
gpointer base = *(gpointer*)user_data;
gpointer end = *(gpointer*)(user_data + sizeof(gpointer));
while (gum_stalker_iterator_next(iterator, &insn))
{
gboolean in_target = (gpointer)insn->address >= base &&
(gpointer)insn->address address,
insn->mnemonic, insn->op_str);
// 注册 before callout
gum_stalker_iterator_put_callout(iterator,
on_arm64_before,
(gpointer)insn->address,
NULL);
}
gum_stalker_iterator_keep(iterator);
if(in_target)
{
// 注册 after callout
gum_stalker_iterator_put_callout(iterator,
on_arm64_after,
(gpointer)insn->address,
NULL);
}
}
}
2. Callout 函数
static void
on_arm64_before(GumCpuContext *cpu_context, gpointer user_data)
{
// 空 - 不做任何操作
}
static void
on_arm64_after(GumCpuContext *cpu_context, gpointer user_data)
{
// 输出所有寄存器,Python 端做比较
// 格式: addr|x0|x1|x2|...|x28|fp|lr|sp|pc
if (cpu_context) {
log("%p|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|"
"%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|"
"%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|%llx|"
"%llx|%llx|%llx|%llx",
user_data,
cpu_context->x[0], cpu_context->x[1], cpu_context->x[2], cpu_context->x[3],
cpu_context->x[4], cpu_context->x[5], cpu_context->x[6], cpu_context->x[7],
cpu_context->x[8], cpu_context->x[9], cpu_context->x[10], cpu_context->x[11],
cpu_context->x[12], cpu_context->x[13], cpu_context->x[14], cpu_context->x[15],
cpu_context->x[16], cpu_context->x[17], cpu_context->x[18], cpu_context->x[19],
cpu_context->x[20], cpu_context->x[21], cpu_context->x[22], cpu_context->x[23],
cpu_context->x[24], cpu_context->x[25], cpu_context->x[26], cpu_context->x[27],
cpu_context->x[28],
cpu_context->fp, cpu_context->lr, cpu_context->sp, cpu_context->pc);
}
}
3. 数据传输格式
通过不同的分隔符区分两种消息:
指令信息(tab 分隔):
0x75669d71c0 add x0, x1, x2
寄存器信息(管道分隔,34 个字段):
0x75669d71c0|f1|7828a7654c|7828a7654c|7fdce415f8|30|7fdce414a0|...|7fdce41630|75669d71c0
4. Python 端处理
def on_message(self, payload):
type = payload['type']
if type == 'c_output':
message = payload['message']
if '|' in message:
# 寄存器信息
parts = message.split('|')
if len(parts) == 34:
addr = parts[0]
x_regs = [int(parts, 16) for i in range(1, 30)]
fp = int(parts[30], 16)
lr = int(parts[31], 16)
sp = int(parts[32], 16)
pc = int(parts[33], 16)
ctx = Arm64Ctx(pc, sp, *x_regs, fp, lr)
self.inst_dict[addr].add_execed_ctx(ctx)
self.inst_dict[addr].cal_regs_change(self.pre_ctx, ctx)
self.pre_ctx = ctx
elif '\t' in message:
# 指令信息
parts = message.split('\t')
if len(parts) == 3:
addr, mnem, op = parts
inst = Inst(addr, addr, 4, mnem, op)
self.inst_dict[addr] = inst
性能对比
[table]
[tr]
[td]场景[/td]
[td]JS 实现[/td]
[td]C 实现[/td]
[/tr]
[tr]
[td]Transform 阶段

