c-sktrace:基于 C 实现的 sktrace 性能优化 demo

查看 15|回复 0
作者:vw2x   
c-sktrace:基于 C 实现的 sktrace 性能优化
前言
这是一个学习性质的项目,研究如何通过 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 次的循环
  • 每次循环 before + after callout = 2 次切换
  • 总计:20,000 次 Native ↔ JS 引擎切换

    引擎切换的开销
    每次切换需要:
    [ol]
  • 保存/恢复 CPU 上下文(所有寄存器)
  • 切换 JS 引擎执行上下文
  • 数据序列化/反序列化(context 对象 → JSON)
  • 进程间通信
    [/ol]
    还是很大的性能消耗
    解决方案:CModule + Python 异步处理
    核心思路
    既然无法消除所有 JS 交互,那就:
    [ol]
  • C 层:只负责快速读取并输出寄存器值
  • Python 层:异步接收数据并进行比较、统计
    [/ol]
    优势
  • ✅ 消除 JSON 序列化开销
  • ✅ 使用简洁的管道分隔格式
  • ✅ C 层只做读取,稳定高效
  • ✅ Python 异步处理不阻塞 trace

    实现细节
    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 阶段

    性能, 指令

  • 您需要登录后才可以回帖 登录 | 立即注册

    返回顶部