喝完几杯新鲜的 ☕,又堆积了一大批提交后,我们带着一个功能丰富的版本回来了。亮点包括更聪明的编译器、更稳固的 Darwin 内部实现,以及大规模的 Swift 重构——绑定现在优先使用 async/await,不再依赖委托,并且大体上与平台无关。
亮点
-
compiler:向
CompilerOptions添加platform和externals选项,并将它们一路传递到 Go 后端。这让 frida-compile(以及Frida.Compiler)能针对目标平台定制输出,并将选定的模块视为外部依赖——例如为 GumJS agent 构建插件时,agent 公开的 API 应在运行时链接,而不是打包进去。(感谢 @leonitousconforti) -
darwin:重写
query_shared_cache_range(),改为解析 dyld 共享缓存头,而不是从AllImageInfos中找到的基址开始遍历虚拟内存区域。这消除了猜测,并确保页面发生写时复制后仍能得到正确的区间。(感谢 @hsorbo 参与结对编程) -
darwin:
AllImageInfos现在会报告 Dyld Shared Cache 的 UUID 和滑动值。(感谢 @hsorbo 参与结对编程) -
simmy:
spawn()现已正确接入argv和env,因此模拟器的行为更接近真实设备。 -
frida-node:生成的
from_value()辅助函数现在会包含继承的属性,因此externals等选项能正确传递。 -
frida-python:修复
PackageManager规格选项解析中一处很小但会泄漏的角落问题。
Swift 绑定:现代化的跨平台革新 🍎
Frida Swift 绑定已经进行了大规模重构,使其符合 Swift 习惯、优先并发,并支持跨平台。
-
全面采用 async/await——大多数 API 现在使用 Swift Concurrency,并通过
GCancellable支持取消Task。 -
移除委托——基于委托的回调已替换为 异步事件流(
AsyncStream),使事件处理更符合习惯,也更易于组合。 -
线程友好——
api: Support invocation from any thread允许从非主线程安全调用。(感谢 @hsorbo 参与结对编程。) -
纯 Swift 核心与跨平台支持——核心绑定现已不依赖 Foundation 和 Dispatch,并使用纯 Swift 类型(二进制数据表示为
[UInt8])。目前仍有两个小缺口:Marshal 辅助函数中的 JSON 编解码当前使用 Foundation;以后会添加不依赖 Foundation 的后备实现。 -
SwiftUI 友好——新增
DeviceListModel,它是一个@MainActor ObservableObject,公开@Published devices和discoveryState,便于顺滑集成 SwiftUI。 -
图标可移植性——用可移植的
Icon枚举取代特定于平台的图像处理,并为CGImage、NSImage、UIImage和SwiftUI.Image提供平台适配器。 -
API 稳定性改进——公开枚举标注了
@frozen,并在必要时将一些复杂引用类型标记为@unchecked Sendable。
注:本版本不包含
frida-swift预构建二进制文件;如果你使用 Swift 绑定,应执行git clone并从main构建,以获得最新变更。还请注意,Swift 绑定仍然处于实验和演进阶段——虽然新 API 向前迈出了一大步,但在即将推出的版本中 Swift 层稳定之前,它们可能还会继续变化。
快速示例(来自 frida-swift README)
DeviceListModel(界面友好的模型):
import Combine
@MainActor
public final class DeviceListModel: ObservableObject {
@Published public private(set) var devices: [Device] = []
@Published public private(set) var discoveryState: DiscoveryState = .discovering
@frozen
public enum DiscoveryState: Equatable {
case discovering
case ready
}
public let manager: DeviceManager
public init(manager: DeviceManager) { … }
}
现在可以这样使用:
import Frida
import SwiftUI
struct DevicesView: View {
@StateObject private var model = DeviceListModel(manager: DeviceManager())
@State private var selectedDevice: Device?
@State private var session: Session?
var body: some View {
NavigationStack {
List(model.devices, id: \.id) { device in
Button {
Task {
selectedDevice = device
session = try? await device.attach(to: 12345)
}
} label: {
VStack(alignment: .leading) {
Text(device.name)
.font(.headline)
Text(device.kind.rawValue)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
}
.navigationTitle("Frida Devices")
.overlay {
if model.devices.isEmpty {
ProgressView("Searching for devices…")
}
}
}
}
}
完整脚本生命周期示例:
func testFullCycle() async throws {
let manager = DeviceManager()
for await devices in await manager.snapshots() {
guard let local = devices.first(where: { $0.kind == .local }) else {
continue
}
let session = try await local.attach(to: 12345)
let script = try await session.createScript("""
console.log("hello");
send(1337);
""")
Task {
for await event in script.events {
switch event {
case .message(let message, _):
print("Message:", message)
case .destroyed:
print("Script destroyed")
}
}
}
try await script.load()
break
}
}
祝使用愉快。一如既往,如果遇到任何问题,请告诉我们!
oleavr