又到发布时间了,这个版本塞满了各种改进!🎉

构建系统

多年来,我一直对我们的构建系统状况不满意,现在终于鼓起勇气对它进行了大幅重构。

用户体验

让我困扰的一点是,我们的构建系统感觉非常古怪而复杂。运行 make 后会看到一个可构建项目的菜单,但菜单会随操作系统而不同。更改构建选项要么编辑 config.mk,要么在命令行中覆盖它们。如果使用 Windows,还必须使用 Visual Studio(MSVS)。

MSVS 构建系统现已移除,你可以像构建许多其他开源项目一样构建 Frida:

$ ./configure
$ make

如果不需要传入 –prefix 等选项,可以跳过 configure 步骤。该脚本其实只是 Meson setup 命令的薄封装;在 -- 后添加选项,即可直接传给 Meson。也可以运行 make install 安装,它支持用 DESTDIR 环境变量更改输出位置。

交叉编译也很简单。例如要为 iOS/arm64 构建,可以使用 –host=ios-arm64 调用 configure。如果有嵌入式系统的工具链,请传入 –host=$triplet,它会在 PATH 中查找 $triplet-gcc、$triplet-g++ 等。你也可以像在其他构建系统中一样,在环境中添加 CFLAGS、LDFLAGS 等以传入额外标志。

很酷的是,我们的各个仓库现在可以独立构建。你可以克隆 frida-gum 仓库并构建,frida-python、frida-tools 等也一样。frida 仓库不再那么重要;保留它只是为了对一组仓库进行版本管理,并托管发布新版本所用的 CI。

每个仓库都包含所需的 subprojects/*.wrap 文件,用于告诉 Meson 在哪里找到依赖项。这意味着,如果你获取 frida-core 并尝试构建,而 PKG_CONFIG_PATH 中尚无 frida-gum,它将自动获取并构建 frida-gum。另一件很棒的事是,我们的 Python 和 Node.js 绑定现在也会利用此机制,在找不到或无法下载 .whl 或预构建时从源码构建。

既然各个组件现在可以作为子项目良好工作,任何人也都可以轻松地将 Frida 组件集成到自己的项目中。只需向项目顶层源码目录的 subprojects/ 添加 .wrap 文件,并在 meson.build 中调用 dependency()。Meson 会先在系统中查找;如果找不到依赖,它会将 git 仓库克隆到 subprojects/ 中,作为项目的一部分进行构建。也可以告诉 Meson 强制使用这种回退,不在系统中查找。

Gum 的 .wrap 文件可以是这样:

[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 16.2.4
depth = 1

[provide]
dependency_names = frida-gum-1.0, frida-gum-heap-1.0, frida-gum-prof-1.0, frida-gumjs-1.0, frida-gumjs-inspector-1.0

frida-core 则可以是:

[wrap-git]
url = https://github.com/frida/frida-core.git
revision = 16.2.4
depth = 1

[provide]
dependency_names = frida-core-1.0

我们的 Meson 薄封装也很容易上手,可在这里详细了解。

一点历史

我之所以选择为 Windows 维护独立的构建系统,是因为以前与不少经验丰富的 Windows 开发者合作过,并注意到如果能使用他们喜爱的 IDE,他们会对开源项目兴趣大增。对他们而言,重要的是能在调试器中查看崩溃,跳到属于开源库的栈帧,添加一些临时日志代码,然后按下“Run”快捷键,让 IDE 增量编译并重新链接一切,形成简短畅快的反馈循环。

当时 Frida 的非 Windows 构建系统是 autotools,后来我们迁移到 Meson。虽然 Meson 有 MSVS 后端,可以为我们生成 MSVS 项目文件,但有一个问题阻碍了这么做。MSVS 可在同一个“solution”(工作区)中混合针对不同机器的项目,而 Meson 除构建机外,一次只支持为一种机器编译。由于 Frida 在 Windows 上需要支持向 32 位和 64 位目标注入代码,移除 MSVS 构建系统会导致可用性倒退。因此我一直对放弃 MSVS 构建系统犹豫不决。

非 Windows 平台也有同样的挑战。我最终写了一个为每种架构调用 Meson 的 Makefile,把它们粘合起来。虽然这能工作,但我始终没能让增量构建也可靠运行。不过,今年 3 月下旬,我终于在 frida-core 内部确定了一种本地解法——它是我们唯一需要这种多架构“疯狂操作”的组件。通过在 custom_target() 中递归调用 Meson,我们可以为其他架构构建所需组件。

要让它正确工作耗费了大量精力,但完成后,其余技术栈的开发体验变得好得多。frida 仓库的 Makefile 和 shell 脚本可以删除并换成 Meson。CI 变得更简单、更统一。任何人只需克隆 frida-core 并运行 make,生成的二进制文件就会支持跨架构。(除非通过 –disable-compat / -Dfrida-core:compat=disabled 显式禁用。)

你可能会想:既然现在只使用 Meson,为什么还要运行 make?我最终编写了一层 Meson 薄封装,用于自动下载预构建依赖项、选择合理的链接器标志以生成更小的二进制文件等。各仓库中新的 configure 和 Makefile 文件负责调用 releng/meson_configure.py 和 releng/meson_make.py,它们共同构成 Meson 薄封装。releng 目录是 Frida 各仓库共享的子模块。在调用封装之前,还会确保 releng 子模块已初始化并更新。

维护

在此版本之前,我们维护着七种不同的构建系统:

  1. Meson(非 Windows 上的主要组件)
  2. Visual Studio(Windows 上的主要组件)
  3. GNU Make(非 Windows 上的主要组件元构建系统)
  4. setuptools(Python 绑定)
  5. GYP(Node.js 绑定)
  6. Xcode(Swift 绑定)
  7. QMake(QML 绑定)

自此版本起,我很高兴宣布,我们现在只剩一个构建系统:Meson。

旧状况的主要痛点是必须同时处理 1)和 2),因为所有主要组件都涉及这两者。这意味着,像添加一个源文件这么简单的事,都需要了解两种不同的构建系统。不仅如此,例如 Linux 上的贡献者通常很难测试自己对 Visual Studio 构建系统的改动。

另一个问题是,如果只是添加一个新文件都要处理两个构建系统,开发者就更不愿意重构代码。长期后果是它滋生了坏习惯。“嗯,我应该把这部分拆到单独文件里,但是……不,那太痛苦了,我暂时还是把代码加在这里吧。”

至于剩余那些只用于绑定的构建系统,维护它们似乎没那么糟——毕竟修改这些代码的人通常熟悉它们。这里的挑战在于,除 QMake 外它们都缺少 pkg-config 集成。这意味着 include 路径、库路径和要链接的库会在多个地方重复。当链接内部又使用其他库的静态库时,情况尤其棘手;而我们的语言绑定通常就是这样使用 frida-core。

新功能

此版本有不少令人兴奋的新内容:

  • gumjs:添加 Process.runOnThread(),便于在特定线程上运行任意代码。必须谨慎使用,以避免死锁/重入问题。
  • gumjs:向 Thread.backtrace() 添加 limit 选项。感谢 @davinci-tech!
  • gumjs:用已弃用 API 的替代品扩展 CModule glib.h。
  • stalker:添加 StalkerIterator.put_chaining_return()。感谢 @s1341!
  • stalker:添加 run_on_thread() 和 run_on_thread_sync()。
  • interceptor:添加对 x86 影子栈的支持。感谢 @yjugl!
  • cpu-features:添加 CET_SS 标志和检测逻辑。感谢 @yjugl!
  • x86-writer:添加 cpu_features 字段。感谢 @yjugl!
  • spinlock:添加 try_acquire()。感谢 @mrmacete!
  • cloak:添加 with_lock_held() 和 is_locked()。感谢 @mrmacete!
  • interceptor:添加 with_lock_held() 和 is_locked()。感谢 @mrmacete!
  • darwin:当 helper 崩溃时提示 macOS 启动参数。
  • java:支持在不调用构造函数的情况下实例化类。感谢 @AeonLucid!
  • java:添加对数组的数组的支持。感谢 @histausse!
  • python:让源码发行包可完全从源码构建,而不是只支持使用 devkit 构建。
  • python:移除 MSVS 构建系统。
  • node:添加 Meson 构建系统,移除 prebuild 和 node-gyp 部分。
  • node:找不到预构建时,支持完全从源码构建。
  • clr:添加 Meson 构建系统,移除 MSVS 构建系统。
  • qml:添加 Meson 构建系统,移除 qmake 构建系统。
  • qml:添加对 Qt 6 的支持。感谢 @zaxo7!
  • qml:停止支持 Qt 5。

错误修复

最后但同样重要的是,我们还带来了一长串质量改进:

  • gumjs:在 NativeCallback 调用前后保留线程的系统错误。感谢 @HexKitchen!
  • gumjs:始终向 NativeCallback 暴露线程的系统错误。
  • gumjs:修复 Stalker 实例泄漏。
  • stalker:修复 arm64 上块事件干扰独占访问的问题。感谢 @saicao!
  • memory:修复 RWX 系统上 patch_code() 的保护翻转。
  • swift-api-resolver:修复对间接类型条目的处理。
  • swift:规避 iOS Simulator 上的 Module.load() 问题。感谢 @zydeco!
  • base:修复自定义 GSource 实现中的竞态崩溃。
  • base:修复 p2p AgentSession 注册逻辑。
  • buffer:修复 read_string() 的大小逻辑。感谢 @hsorbo!
  • linux:修复某些 32 位系统上的早期插桩。
  • linux:修复现代 Android 上的 inject_library_blob()。
  • linux:修复不可靠的 exec 转换逻辑。
  • linux:修复不存在 libc 时不可靠的注入。
  • agent:修复 child-gating fork() 场景中的挂起。当 pidfd_getfd() 可用但不允许使用时,例如 Docker 容器内,可复现此问题。
  • windows:为注入使用 RW/RX 权限。这使 Frida 注入与更多软件兼容。特别是 Mozilla Firefox 会在启动地址为 RWX 时拒绝线程启动。感谢 @yjugl!
  • darwin:在 Frida 钩子周围调整 libunwind,以避免破坏使用异常的代码,例如 Objective-C 中通过 @try/@catch 使用异常的代码。感谢 @mrmacete!
  • darwin:在 ThreadSuspendMonitor 中获取 Interceptor 和 Cloak 锁,扩展其作用范围,防止持有 Cloak 或 Interceptor 锁的线程被暂停时出现死锁。感谢 @mrmacete!
  • darwin:修复 InjectInstance dispatch source 拆除时的竞态。
  • darwin:修复 SpawnInstance dispatch source 拆除时的竞态。
  • android:修复 execl() 及相关函数的 child-gating。
  • compiler:升级 @types/frida-gum。感谢 @s1341!
  • modulate:在缺少符号时优雅处理。感谢 @hsorbo!
  • python:将 _frida 包移到 frida 包内。

文末

以上差不多就是全部内容。尽情享用!