一天发布两个版本?软件开发很难,API 设计更难。但这次值得:@hsorbo 让 Swift ApiResolver 学会查找类型、协议和协议遵循关系;新的 PatternCompiler API 也重塑为基于文件工作,使 pattern 在主机端也能相互导入。
Swift
Frida 的 ApiResolver(‘swift’) 已推出一段时间,可用 glob 按还原后的名称查找 Swift 函数。它在幕后遍历编译器写入每个 Swift 二进制文件的 Swift 元数据,类型和协议信息也存放在那里。Håvard 从 2023 年起持续改进这个解析器;在 17.20.0 中,他改用运行 Swift 代码时始终存在的 libswiftCore 来还原名称,而不是通常不存在的 libswiftDemangle,使其可在更多进程中工作。
在此基础上,本版本新增三类查询。首先,types: 和 protocols: 按完整名称匹配名义类型和协议,并返回上下文描述符的地址:
const resolver = new ApiResolver('swift');
for (const { name, address } of resolver.enumerateMatches('types:*!Swift.Int'))
console.log(name, address);
for (const { name, address } of resolver.enumerateMatches('protocols:*!Swift.Hashable'))
console.log(name, address);/usr/lib/swift/libswiftCore.dylib!Swift.Int 0x19bf8009c
/usr/lib/swift/libswiftCore.dylib!Swift.Hashable 0x19bf7c490与其他解析器一样,! 前面的部分匹配模块,因此 types:*libswiftCore*!Swift.Dictionary* 可将范围缩小到特定库,并查找嵌套类型:
/usr/lib/swift/libswiftCore.dylib!Swift.Dictionary 0x19bf7abe8
/usr/lib/swift/libswiftCore.dylib!Swift.Dictionary.Keys 0x19bf7ac54
/usr/lib/swift/libswiftCore.dylib!Swift.Dictionary.Values 0x19bf7ac90
/usr/lib/swift/libswiftCore.dylib!Swift.Dictionary.Keys.Iterator 0x19bf7accc
/usr/lib/swift/libswiftCore.dylib!Swift.Dictionary.Values.Iterator 0x19bf7ad08其次,conformances: 按类型名和协议名匹配协议遵循关系,并返回遵循关系描述符的地址。这是我最兴奋的功能,因为它能回答“这个类型实现了哪些协议?”之类的问题:
for (const { name, address } of resolver.enumerateMatches('conformances:Swift.Int!Swift.*'))
console.log(name, address);Swift.Int!Swift.Encodable 0x19bf5f6c4
Swift.Int!Swift.Decodable 0x19bf5f6d4
Swift.Int!Swift.CodingKeyRepresentable 0x19bf5f984
Swift.Int!Swift.CustomReflectable 0x19bf612ec
Swift.Int!Swift._CustomPlaygroundQuickLookable 0x19bf612fc
...反过来也能回答“哪些类型实现了这个协议?”,这在大型应用中尤其有趣:
const encodable = resolver.enumerateMatches('conformances:*!Swift.Encodable');
console.log(encodable.length, 'types conform to Encodable');
for (const { name, address } of encodable.slice(0, 3))
console.log(name, address);3735 types conform to Encodable
UIIntelligenceSupport.IntelligenceElement.Axis!Swift.Encodable 0x2a5e0a1c8
UIIntelligenceSupport.IntelligenceElement.Image!Swift.Encodable 0x2a5e0a9d0
UIIntelligenceSupport.IntelligenceElement.CustomAppEntity!Swift.Encodable 0x2a5e0b0e8从遵循关系描述符可以找到 witness table,从上下文描述符则可找到类型的元数据、字段和方法。因此,它们是任何希望在运行时理解 Swift 程序类型的工具的基础构件,从美化输出某个值,到 Hook 实现特定协议的每个类型。未来会有更多功能构建在其上。
他还修复了 /i 后缀;Swift 解析器此前虽然接受它,却没有真正生效。非常感谢 Håvard 完成这些出色工作!
PatternCompiler
今天早些时候的版本引入了 PatternCompiler API,可在主机上编译 pattern,并据此解码内存。它原本接收源码字符串,看似方便,却无法知道源码位于何处,因此 pattern 除 std 库外不能导入任何内容。现在已经修复,API 与 Compiler.build() 对齐:传入路径,还可选择传入项目根目录;省略后者时会根据入口点推断。假设有 proj/player.hexpat,它从 proj/common.pat 导入 Vec3:
#pragma abi native
import common;
struct Player {
u32 health;
Vec3 position;
};导入解析方式与 Frida.Compiler 构建 Agent 时完全相同:先相对于导入文件解析,再从 node_modules 中解析。现在每种类型都会说明其声明文件,每条诊断也一样;路径相对于项目根目录,与构建诊断的报告方式相同:
import frida
module = frida.PatternCompiler().compile("proj/player.hexpat")
for d in module.diagnostics:
print(f"{d.file}:{d.line + 1}:{d.character + 1}: {d.message}")
for t in module.types:
print(f"{t.file}: {t.kind} {t.name}, {t.size} bytes")如果 common.pat 缺少分号,会报告:
proj/common.pat:5:1: expected ;修复后则输出:
proj/common.pat: struct Vec3, 12 bytes
proj/player.hexpat: struct Player, 16 bytes语言服务器也进行了相同改造,因此 .hexpat 导入另一标签页中尚未保存的缓冲区时也能解析,而且问题仍归属其所在文件。Swift 绑定已相应重新生成,其他绑定会自动获得这些变化。
这是对今天早上刚发布 API 的破坏性变更,希望尚无人基于它构建内容。如果已经使用,迁移方法是把 pattern 写入文件并传入其路径。
结语
尽情体验吧!
变更日志
- swift-api-resolver:新增 types: 和 protocols: 查询,按完整名称匹配 __swift5_types、__swift5_types2 和 __swift5_protos 中的描述符。感谢 @hsorbo!
- swift-api-resolver:新增 conformances: 查询,按类型名和协议名匹配 __swift5_proto 记录,并让 /i 真正生效。感谢 @hsorbo!
- compiler:从文件编译 pattern;导入和 include 的解析方式与 Agent 构建相同;每种类型和诊断的文件名都相对于项目根目录。(上文已介绍。)
- compiler:修复项目根目录为相对路径时的崩溃;此前 TypeScript 编译器虚拟文件系统内部的错误会导致整个进程退出。
- swift:重新生成 PatternCompiler 绑定。
oleavr