Metal Library Archive(Metal 库归档)

MetalLibraryArchive 是对 Apple 的 metallib 文件格式进行逆向工程的产物。

您可以使用 MetalLibraryArchivemetallib 文件中获取库类型、目标平台、Metal 函数等信息。

提取的 Metal 函数信息包括:

🎈 使用方法

Web App(网页应用)

可用地址:https://yuao.github.io/MetalLibraryExplorer

了解更多

Explorer App(资源管理器应用)

软件包中包含一个名为 "Explorer" 的可执行目标。"Explorer" 是一个 GUI 应用程序,可以打开、解包和反汇编(借助 llvm-dismetallib 文件。

注意:不包含 llvm-dis,您可以在 https://github.com/llvm/llvm-project/releases 获取该二进制文件的副本。

使用应用程序中的 "Disassembler"(反汇编器)菜单来定位 llvm-dis 可执行文件。

Screenshot

Library(库)

您也可以将 MetalLibraryArchive 用作库。

import MetalLibraryArchive

let archive = try Archive(data: Data(contentsOf: metallibURL))
let libraryType = archive.libraryType
let functions = archive.functions

🚧 Metal Library Archive 二进制布局

Header(头部)

Byte Range(字节范围) Type(类型) Content(内容)
0...3 FourCharCode MTLB
4...5 UInt16 Target platform(目标平台)
6...9 (UInt16, UInt16) Version of the metallib file (major, minor)(metallib 文件的版本(主版本,次版本))
10 UInt8 Type of the metallib file(metallib 文件的类型)
11 UInt8 Target OS(目标操作系统)
12...15 (UInt16, UInt16) Version of the target OS (major, minor)(目标操作系统的版本(主版本,次版本))
16...23 UInt64 Size of the metallib file(metallib 文件的大小)
24...39 (UInt64, UInt64) Offset and size of the function list(函数列表的偏移量和大小)
40...55 (UInt64, UInt64) Offset and size of the public metadata section(公共元数据部分的偏移量和大小)
56...71 (UInt64, UInt64) Offset and size of the private metadata section(私有元数据部分的偏移量和大小)
72...87 (UInt64, UInt64) Offset and size of the bitcode section(Bitcode 部分的偏移量和大小)
Target Platform(目标平台) Value(值)
macOS 0x8001 (0x01,0x80)
iOS 0x0001 (0x01,0x00)
metallib Type(metallib 类型) Value(值)
Executable(可执行) 0x00
Core Image 0x01
Dynamic(动态) 0x02
Symbol Companion(符号伴侣) 0x03
Target OS(目标操作系统) Value(值)
Unknown(未知) 0x00
macOS 0x81
iOS 0x82
tvOS 0x83
watchOS 0x84
bridgeOS (Probably)(可能) 0x85
macCatalyst 0x86
iOS Simulator(iOS 模拟器) 0x87
tvOS Simulator(tvOS 模拟器) 0x88
watchOS Simulator(watchOS 模拟器) 0x89

Function List(函数列表)

Byte Range(字节范围) Type(类型) Content(内容)
0...3 UInt32 Entry count (the number of functions)(条目计数(函数数量))
4... Tag Groups(标签组) Each tag group holds some information about a Metal function(每个标签组保存有关 Metal 函数的一些信息)

The number of tag groups equals the number of functions.(标签组的数量等于函数的数量。)

Tag Group(标签组)

Byte Range(字节范围) Type(类型) Content(内容)
0...3 UInt32 Size of the tag group(标签组的大小)
4... Tags(标签)

Tag(标签)

Byte Range(字节范围) Type(类型) Content(内容)
0...3 FourCharCode Name of the tag(标签的名称)
4...5 UInt16 Size of the tag(标签的大小)
6... Bytes(字节) Content of the tag(标签的内容)

Function Information Tags(函数信息标签)

Name(名称) Content Data Type(内容数据类型) Content(内容)
NAME NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) Name of the function(函数的名称)
MDSZ UInt64 Size of the bitcode(Bitcode 的大小)
TYPE UInt8 Type of the function(函数的类型)
HASH SHA256 Digest(SHA256 摘要) Hash of the bitcode data (SHA256)(Bitcode 数据的哈希值(SHA256))
OFFT (UInt64, UInt64, UInt64) Offsets of the information about this function in the public metadata section, private metadata section, and bitcode section(此函数在公共元数据部分、私有元数据部分和 Bitcode 部分中的信息偏移量)
SOFF UInt64 Offset of the source code archive of the function in the embedded source code section(函数源代码存档在嵌入式源代码部分中的偏移量)
VERS (UInt16, UInt16, UInt16, UInt16) Bitcode and language versions (air.major, air.minor, language.major, language.minor)(Bitcode 和语言版本(air.major,air.minor,language.major,language.minor))
LAYR UInt8 Metal type(Metal 类型) of the render_target_array_index (for layered rendering(分层渲染))
TESS UInt8 Patch type and number of control points per-patch (for post-tessellation vertex function)(每个补丁的 Patch 类型和控制点数量(用于镶嵌后顶点函数))
ENDT End of the tag group(标签组的结束)
Function Type(函数类型) Value(值) Note(注意)
Vertex(顶点) 0x00
Fragment(片段) 0x01
Kernel(内核) 0x02
Unqualified(无限定) 0x03 Functions in Metal dynamic library(Metal 动态库中的函数)
Visible(可见) 0x04 Functions with [[visible]] or [[stitchable]] attributes(具有 [[visible]][[stitchable]] 属性的函数)
Extern(外部) 0x05 Extern functions complied with -fcikernel option(使用 -fcikernel 选项编译的外部函数)
Intersection(相交) 0x06

Content of the TESS tag(TESS 标签的内容)

// Patch types:
//   - triangle: 1
//   - quad: 2

let content: UInt8 = controlPointCount << 2 | patchType

Public Metadata(公共元数据)

Contains information about function constants, tessellation patches, return types, etc.(包含有关函数常量、镶嵌补丁、返回类型等的信息。)

Tags: CNST, VATT, VATY, RETR, ARGR, etc.

Private Metadata(私有元数据)

Contains paths to the shader source (DEBI tag) and .air (DEPF tag) files.(包含着色器源代码(DEBI 标签)和 .airDEPF 标签)文件的路径。)

Header Extension(头部扩展)

Only exists if FunctionListOffset + FunctionListSize + 4 != PublicMetadataOffset(仅当 FunctionListOffset + FunctionListSize + 4 != PublicMetadataOffset 时存在)

Byte Range(字节范围) Type(类型) Content(内容)
FunctionListOffset + FunctionListSize + 4... Tags(标签) Header extension tags(头部扩展标签)

Header Extension Tags(头部扩展标签)

Name(名称) Type(类型) Content(内容)
HDYN (UInt64, UInt64) Offset and size of the dynamic header section(动态头部部分的偏移量和大小)
VLST (UInt64, UInt64) Offset and size of the exported variable list(导出的变量列表的偏移量和大小)
ILST (UInt64, UInt64) Offset and size of the imported symbol list(导入的符号列表的偏移量和大小)
HSRD/HSRC (UInt64, UInt64) Offset and size of the embedded source code section(嵌入式源代码部分的偏移量和大小)
UUID UUID UUID of the Metal library.(Metal 库的 UUID。)
ENDT End of the header extension(头部扩展的结束)

Dynamic Header Section Tags(动态头部部分标签)

Name(名称) Content Data Type(内容数据类型) Content(内容)
NAME NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) Install name of the library(库的安装名称)
DYNL NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) Linked dynamic library(链接的动态库)

Variable List & Imported Symbol List(变量列表 & 导入的符号列表)

Variable list and imported symbol list have structures that are similar to that of the function list.(变量列表和导入的符号列表具有与函数列表类似的结构。)

Embedded Source Code Section(嵌入式源代码部分)

Only exists if the metallib build process is configured to include source code(包含源代码)

Byte Range(字节范围) Type(类型) Content(内容)
0...1 UInt16 Number of items in this section(此部分中的项目数)
2...n NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) Link options of the metallib file(metallib 文件的链接选项)
n...m NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) Working directory(工作目录)
m... Tag Group(标签组) SARC tag

Note: "Working directory" only exists in HSRD.(注意: "工作目录" 仅存在于 HSRD 中。)

Note: SARC tag uses 4-bytes (UInt32) content size.(注意: SARC 标签使用 4 字节 (UInt32) 内容大小。)

Content of the SARC tag(SARC 标签的内容)

Byte Range(字节范围) Type(类型) Content(内容)
0...n NULL-terminated C-style string(以 NULL 结尾的 C 风格字符串) ID of the source code archive(源代码存档的 ID)
n... BZh Bzip2 compressed source code archive(Bzip2 压缩的源代码存档)

Metal Data Type Table(Metal 数据类型表)

Value(值) Type(类型) Value(值) Type(类型)
0x00 None(无) 0x01 Struct(结构体)
0x02 Array(数组) 0x03 Float(浮点数)
0x04 Float2 0x05 Float3
0x06 Float4 0x07 Float2x2
0x08 Float2x3 0x09 Float2x4
0x0A Float3x2 0x0B Float3x3
0x0C Float3x4 0x0D Float4x2
0x0E Float4x3 0x0F Float4x4
0x10 Half(半精度浮点数) 0x11 Half2
0x12 Half3 0x13 Half4
0x14 Half2x2 0x15 Half2x3
0x16 Half2x4 0x17 Half3x2
0x18 Half3x3 0x19 Half3x4
0x1A Half4x2 0x1B Half4x3
0x1C Half4x4 0x1D Int(整数)
0x1E Int2 0x1F Int3
0x20 Int4 0x21 UInt(无符号整数)
0x22 UInt2 0x23 UInt3
0x24 UInt4 0x25 Short(短整数)
0x26 Short2 0x27 Short3
0x28 Short4 0x29 UShort(无符号短整数)
0x2A UShort2 0x2B UShort3
0x2C UShort4 0x2D Char(字符)
0x2E Char2 0x2F Char3
0x30 Char4 0x31 UChar(无符号字符)
0x32 UChar2 0x33 UChar3
0x34 UChar4 0x35 Bool(布尔值)
0x36 Bool2 0x37 Bool3
0x38 Bool4 0x3A Texture(纹理)
0x3B Sampler(采样器) 0x3C Pointer(指针)
0x3E R8Unorm 0x3F R8Snorm
0x40 R16Unorm 0x41 R16Snorm
0x42 RG8Unorm 0x43 RG8Snorm
0x44 RG16Unorm 0x45 RG16Snorm
0x46 RGBA8Unorm 0x47 RGBA8Unorm_sRGB
0x48 RGBA8Snorm 0x49 RGBA16Unorm
0x4A RGBA16Snorm 0x4B RGB10A2Unorm
0x4C RG11B10Float 0x4D RGB9E5Float
0x4E RenderPipeline(渲染管线) 0x4F ComputePipeline(计算管线)
0x50 IndirectCommandBuffer(间接命令缓冲区) 0x51 Long(长整数)
0x52 Long2 0x53 Long3
0x54 Long4 0x55 ULong(无符号长整数)
0x56 ULong2 0x57 ULong3
0x58 ULong4 0x59 Double(双精度浮点数)
0x5A Double2 0x5B Double3
0x5C Double4 0x5D Float8
0x5E Float16 0x5F Half8
0x60 Half16 0x61 Int8
0x62 Int16 0x63 UInt8
0x64 UInt16 0x65 Short8
0x66 Short16 0x67 UShort8
0x68 UShort16 0x69 Char8
0x6A Char16 0x6B UChar8
0x6C UChar16 0x6D Long8
0x6E Long16 0x6F ULong8
0x70 ULong16 0x71 Double8
0x72 Double16 0x73 VisibleFunctionTable(可见函数表)
0x74 IntersectionFunctionTable(相交函数表) 0x75 PrimitiveAccelerationStructure(图元加速结构)
0x76 InstanceAccelerationStructure(实例加速结构) 0x77 Bool8
0x78 Bool16

❤️ Contributing(贡献)

如果您认为存在错误,请提出 issue。您也可以选择打开包含失败测试的 pull request。

👾 The Story(背后的故事)

如果没有 zhuowei 的研究,这个项目就不会启动。他的研究揭示了 metallib 文件的基本二进制布局、函数列表以及 Bitcode 部分。感谢 @zhuowei

What the assembly can tell(汇编能告诉我们什么)

我尝试继续研究以获得 metallib 文件的完整结构,但发现仅凭猜测很难取得进展。因此,我将注意力转向 Metal.framework,希望找出该框架如何加载 metallib 文件。幸运的是,将 Metal.framework/Metal 拖到 Hopper Disassembler 后,并不太难。

Metal.framework 使用 MTLLibraryDataWithArchive::parseArchiveSync(...) 加载 metallib 文件。 MTLLibrary

经过一番研究,我大致了解了 metallib 文件的结构。

TDG - "Test Driven Guessing"(测试驱动猜测法)

接下来,我需要弄清楚每个标签/字段保存什么信息。 这很难从 Metal.framework 的汇编中获取,因为

似乎获得此信息的最快方法是通过实验。

我首先手动编译具有不同着色器、选项和 SDK 的 metal 文件,然后检查我感兴趣的每个字段。 我的桌面很快就被 metallib 文件和 HexFiend 窗口淹没了,但我没有找到太多有用的信息。 我需要一些可以自动构建 metallib 并只向我展示我感兴趣的字段的东西。

我想出了“Test Driven Guessing”(测试驱动猜测法)

  1. 基于手头的二进制结构概述编写一个 metallib 解析器。

  2. 在解析器中,记录当前未知的字段/标签(或一些相关字段)的值。

  3. 创建测试,使用不同类型的着色器和可能影响字段值的编译选项生成 metallib 文件,并使用解析器解析文件数据。

  4. 运行测试并分析日志以做出假设。

  5. 根据假设更新解析器。

  6. 再次运行测试进行验证。

经过几轮,我能够得到函数类型表、目标操作系统表以及 OFFT 标签中 3 个偏移量的含义。

我也在这个过程中发现了一些有趣的事情:

更新

2022 年 4 月 10 日

LAYRVATYCNST 等标签包含 Metal 数据类型的 UInt8 值。 可以使用 Metal.framework 中的一个私有类检索每个数据类型值的对应描述 - MTLTypeInternal

id value = [[NSClassFromString(@"MTLTypeInternal") alloc] initWithDataType:0x06];
NSLog(@"%@", value.description); // MTLDataTypeFloat4

我创建了一个命令行工具来生成 Metal 数据类型表。

cd Utilities
swift run metal-data-type-tools gen-markdown --columns 2 # generate a markdown table
swift run metal-data-type-tools gen-swift # generate a Swift enum for Metal data types.

2022 年 3 月 31 日

air-lld (Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/metal/ios/bin/air-lld) 也提供了很多关于 metallib 文件是如何构建的信息。 一些节名称和描述已更新。

int __ZN4llvm3air20MetalLibObjectWriter5writeEv() {
    r14 = rdi;
    rax = llvm::air::MetalLibObjectWriter::writeHeader();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035135:
    rax = llvm::air::MetalLibObjectWriter::writeFunctionList();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035141:
    rax = llvm::air::MetalLibObjectWriter::writeHeaderExtension();
    if (rax != 0x0) goto loc_1000351b9;

loc_10003514d:
    rax = llvm::air::MetalLibObjectWriter::writePublicMetadata();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035159:
    rax = llvm::air::MetalLibObjectWriter::writePrivateMetadata();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035165:
    rax = llvm::air::MetalLibObjectWriter::writeModuleList();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035171:
    rax = llvm::air::MetalLibObjectWriter::writeSources();
    if (rax != 0x0) goto loc_1000351b9;

loc_10003517d:
    rax = llvm::air::MetalLibObjectWriter::writeDynamicHeader();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035189:
    rax = llvm::air::MetalLibObjectWriter::writeVariableList();
    if (rax != 0x0) goto loc_1000351b9;

loc_100035195:
    rax = llvm::air::MetalLibObjectWriter::writeImportedSymbolList();
    if (rax != 0x0) goto loc_1000351b9;

loc_1000351a1:
    rax = llvm::air::MetalLibObjectWriter::computeUUID();
    if (rax != 0x0) goto loc_1000351b9;

loc_1000351ad:
    rax = llvm::air::MetalLibObjectWriter::backpatchAllLocations();
    if (rax == 0x0) goto loc_1000351c2;

loc_1000351b9:
    rbx = rax;
    goto loc_1000351bb;

loc_1000351bb:
    rax = rbx;
    return rax;

loc_1000351c2:
    rbx = 0x0;
    std::__1::system_category();
    goto loc_1000351bb;
}