MetalLibraryArchive是反设计苹果的metallib文件格式的产物。
您可以使用MetalLibraryArchive从metallib文件中获取库类型,目标平台,金属功能等。
金属功能的提取信息包括:
metallib配置为包括源代码。 可在以下网址获得:https://yuao.github.io/metallibraryexplorer
了解更多
软件包中包含一个称为“ Explorer”的可执行目标。 “ Explorer”是一个GUI应用程序,可以打开,解开和拆卸(借助llvm-dis ) metallib文件。
注意不包括llvm-dis ,您可以在https://github.com/llvm/llvm/llvm-project/releases上获得二进制的副本
使用应用程序中的“拆卸器”菜单来找到llvm-dis可执行文件。

您也可以将MetalLibraryArchive用作库:
import MetalLibraryArchive
let archive = try Archive ( data : Data ( contentsOf : metallibURL ) )
let libraryType = archive . libraryType
let functions = archive . functions| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... 3 | 四键 | mtlb |
| 4 ... 5 | UINT16 | 目标平台 |
| 6 ... 9 | (UINT16,UINT16) | Metallib文件的版本(主要,次要) |
| 10 | UINT8 | Metallib文件的类型 |
| 11 | UINT8 | 目标操作系统 |
| 12 ... 15 | (UINT16,UINT16) | 目标操作系统的版本(主要,次要) |
| 16 ... 23 | Uint64 | Metallib文件的大小 |
| 24 ... 39 | (UINT64,UINT64) | 功能列表的偏移和大小 |
| 40 ... 55 | (UINT64,UINT64) | 公共元数据部分的偏移和大小 |
| 56 ... 71 | (UINT64,UINT64) | 私人元数据部分的偏移和大小 |
| 72 ... 87 | (UINT64,UINT64) | 偏移和比特章节的大小 |
| 目标平台 | 价值 |
|---|---|
| macos | 0x8001(0x01,0x80) |
| ios | 0x0001(0x01,0x00) |
| Metallib类型 | 价值 |
|---|---|
| 可执行 | 0x00 |
| 核心图像 | 0x01 |
| 动态的 | 0x02 |
| 符号伴侣 | 0x03 |
| 目标操作系统 | 价值 |
|---|---|
| 未知 | 0x00 |
| macos | 0x81 |
| ios | 0x82 |
| TVOS | 0x83 |
| 手表 | 0x84 |
| 布里奇斯(可能) | 0x85 |
| 大型催化剂 | 0x86 |
| iOS模拟器 | 0x87 |
| TVOS模拟器 | 0x88 |
| WatchOS模拟器 | 0x89 |
| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... 3 | Uint32 | 输入数(功能数量) |
| 4 ... | 标签组 | 每个标签组都有一些有关金属功能的信息 |
标签组的数量等于函数的数量。
| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... 3 | Uint32 | 标签组的大小 |
| 4 ... | 标签 |
| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... 3 | 四键 | 标签的名称 |
| 4 ... 5 | UINT16 | 标签的大小 |
| 6 ... | 字节 | 标签的内容 |
| 姓名 | 内容数据类型 | 内容 |
|---|---|---|
| 姓名 | 零终端的C风格字符串 | 功能的名称 |
| MDSZ | Uint64 | 比特码的大小 |
| 类型 | UINT8 | 功能类型 |
| 哈希 | SHA256消化 | 比特码数据的哈希(SHA256) |
| OFFT | (UINT64,UINT64,UINT64) | 在公共元数据部分,私人元数据部分和比特码部分中,有关此功能的信息的偏移 |
| 拱立 | Uint64 | 在嵌入式源代码部分中的源代码存档的源代码档案 |
| Vers | (UINT16,UINT16,UINT16,UINT16) | 比特码和语言版本(air.major,air.minor,语言。 |
| Layr | UINT8 | render_target_array_index的金属类型(用于分层渲染) |
| 苔丝 | UINT8 | 每次绘制的补丁类型和控制点的数量(用于插入后顶点函数) |
| 端 | 标签组的结尾 |
| 功能类型 | 价值 | 笔记 |
|---|---|---|
| 顶点 | 0x00 | |
| 分段 | 0x01 | |
| 核心 | 0x02 | |
| 不合格 | 0x03 | 金属动态库中的功能 |
| 可见的 | 0x04 | 具有[[visible]]或[[stitchable]]属性的功能 |
| 外部 | 0x05 | 外部功能符合-fcikernel选项 |
| 路口 | 0x06 |
TESS标签的内容:
// Patch types:
// - triangle: 1
// - quad: 2
let content : UInt8 = controlPointCount << 2 | patchType包含有关功能常数,镶嵌补丁,返回类型等的信息。
标签: CNST , VATT , VATY , RETR , ARGR等。
包含通往着色器源( DEBI标签)和.air ( DEPF标签)文件的路径。
仅在FunctionListOffset + FunctionListSize + 4 != PublicMetadataOffset时存在
| 字节范围 | 类型 | 内容 |
|---|---|---|
FunctionListOffset + FunctionListSize + 4 ... | 标签 | 标题扩展标签 |
| 姓名 | 类型 | 内容 |
|---|---|---|
| Hdyn | (UINT64,UINT64) | 动态标头部分的偏移和大小 |
| VLST | (UINT64,UINT64) | 导出变量列表的偏移和大小 |
| Ilst | (UINT64,UINT64) | 导入符号列表的偏移和大小 |
| HSRD/HSRC | (UINT64,UINT64) | 嵌入式源代码部分的偏移和大小 |
| UUID | UUID | 金属库的UUID。 |
| 端 | 标题延伸的末端 |
| 姓名 | 内容数据类型 | 内容 |
|---|---|---|
| 姓名 | 零终端的C风格字符串 | 安装图书馆名称 |
| dynl | 零终端的C风格字符串 | 链接的动态库 |
可变列表和导入的符号列表具有与功能列表相似的结构。
仅在将metallib构建过程配置为包含源代码的情况下才存在。
| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... 1 | UINT16 | 本节中的项目数量 |
| 2 ... n | 零终端的C风格字符串 | metallib文件的链接选项 |
| n ... m | 零终端的C风格字符串 | 工作目录 |
| M ... | 标签组 | SARC标签 |
注意“工作目录”仅存在于HSRD中。
注意SARC标签使用4个字( UInt32 )内容大小。
SARC标签的内容:
| 字节范围 | 类型 | 内容 |
|---|---|---|
| 0 ... n | 零终端的C风格字符串 | 源代码存档的ID |
| n ... | BZH | BZIP2压缩源代码存档 |
| 价值 | 类型 | 价值 | 类型 |
|---|---|---|---|
| 0x00 | 没有任何 | 0x01 | 结构 |
| 0x02 | 大批 | 0x03 | 漂浮 |
| 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 | 一半 | 0x11 | 半2 |
| 0x12 | 一半3 | 0x13 | 半4 |
| 0x14 | 半2x2 | 0x15 | 半2x3 |
| 0x16 | 半2x4 | 0x17 | 半3x2 |
| 0x18 | 半3x3 | 0x19 | 半3x4 |
| 0x1a | 半4x2 | 0x1b | 半4x3 |
| 0x1c | 半4x4 | 0x1d | int |
| 0x1e | int2 | 0x1f | INT3 |
| 0x20 | INT4 | 0x21 | Uint |
| 0x22 | uint2 | 0x23 | UINT3 |
| 0x24 | Uint4 | 0x25 | 短的 |
| 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 | 布尔 |
| 0x36 | Bool2 | 0x37 | boo3 |
| 0x38 | boo4 | 0x3a | 质地 |
| 0x3b | 采样器 | 0x3c | 指针 |
| 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 | Computepeline |
| 0x50 | IndirectCommandBuffer | 0x51 | 长的 |
| 0x52 | Long2 | 0x53 | long3 |
| 0x54 | long4 | 0x55 | 乌隆 |
| 0x56 | Ulong2 | 0x57 | Ulong3 |
| 0x58 | Ulong4 | 0x59 | 双倍的 |
| 0x5a | double2 | 0x5b | double3 |
| 0x5c | double4 | 0x5d | float8 |
| 0x5e | Float16 | 0x5f | 半8 |
| 0x60 | 半16 | 0x61 | INT8 |
| 0x62 | INT16 | 0x63 | UINT8 |
| 0x64 | UINT16 | 0x65 | Short8 |
| 0x66 | 短16 | 0x67 | USHORT8 |
| 0x68 | Ushort16 | 0x69 | char8 |
| 0x6a | char16 | 0x6b | UCHAR8 |
| 0x6c | UCHAR16 | 0x6d | long8 |
| 0x6e | Long16 | 0x6f | Ulong8 |
| 0x70 | Ulong16 | 0x71 | double8 |
| 0x72 | 双重16 | 0x73 | 可见功能 |
| 0x74 | 交点功能 | 0x75 | primitiveaccelerationstructure |
| 0x76 | InstanceAccelerations结构 | 0x77 | boo8 |
| 0x78 | Bool16 |
如果您认为有错误,请打开一个问题。您还可以选择包含故障测试的拉动请求。
如果没有Zhuowei的研究,该项目就不会开始,该研究揭示了metallib文件的基本二进制布局,功能列表以及比特码部分。谢谢,@zhuowei!
我试图继续研究以获取metallib文件的完整结构,但发现仅凭猜测工作就很难前进。因此,我将注意力转移到了Metal.framework框架希望能找到该框架如何加载metallib文件。幸运的是,拖动Metal.framework/Metal后,这并不难。
Metal.framework使用MTLLibraryDataWithArchive::parseArchiveSync(...)加载metallib文件。 MTLLibraryDataWithArchive的组装中隐藏了很多信息。例如:
该文件从0x424c544d (MTLB)开始;文件的大小记录在Offset 0x10 。
int __ZN25MTLLibraryDataWithArchive16parseArchiveSyncEPP7NSErrorb ( void * * arg0, bool arg1) {
r12 = rdx;
r14 = arg1;
r13 = arg0;
(*(*arg0 + 0xb8 ))(arg0, 0x0 ); // LibraryWithFile::setPosition(...)
r15 = r13 + 0x78 ;
rbx = (*(*r13 + 0xc0 ))(r13, r15, 0x58 ); // LibraryWithFile::readBytes(...)
rax = *r13;
rax = (*(rax + 0xc8 ))(r13); // LibraryWithFile::getFileSize(...)
// 0x424c544d - MTLB
// File size field offset: 0x88 - 0x78 = 0x10
if (((rbx != 0x58 ) || (*( int32_t *)(r13 + 0x78 ) != 0x424c544d )) || (*(r13 + 0x88 ) != rax)) goto loc_6a65b;
...
loc_6a65b:
if (r14 == 0x0 ) goto loc_6a6c5;
loc_6a660:
rdx = @ " Invalid library file " ;
...
}偏移0x4处的Int16值与目标平台有关。
loc_6a610:
// 0x7c - 0x78 = 0x4
rax = *( int16_t *)(r13 + 0x7c ) & 0xffff ;
if ((rax >= 0x0 ) || (r12 == 0x0 )) goto loc_6a6ea;
loc_6a627:
if (r14 == 0x0 ) goto loc_6a6c5;
loc_6a630:
rdx = @ " This library format is not supported on this platform (or was built with an old version of the tools) " ;
goto loc_6a689;有一个“标头扩展部分”,其中包含有关“动态标头”部分,“导入符号列表”和“变量列表”的信息:
if (MTLLibraryDataWithArchive::parseHeaderExtension(r13, r13 + 0x100 , r14) != 0x0 ) {
if ( MTLLibraryDataWithArchive::parseDynamicHeaderSection (r13) != 0x0 ) {
if ( MTLLibraryDataWithArchive::parseImportedSymbolListSection (r13) != 0x0 ) {
rax = MTLLibraryDataWithArchive::parseVariableListSection (r13);
} else {
rax = 0x0 ;
}
} else {
rax = 0x0 ;
}
} else {
rax = 0x0 ;
}使用SHA256验证了比特码。
int ____ZN25MTLLibraryDataWithArchive15validateBitCodeEmmPK6NSDataRK12MTLUINT256_t_block_invoke ( int arg0) {
...
CC_SHA256_Init (&var_B0);
CC_SHA256_Update (&var_B0, r14, *( int32_t *)(r15 + 0x38 ));
CC_SHA256_Final (&var_48, &var_B0);
...
}许多四频道代码:
// 0x454e4454 - ENDT
loc_6a8bc:
if (rax == 0x454e4454 ) goto loc_6a871;
...
// 0x54595045 - TYPE
loc_6a984:
if (rax == 0x54595045 ) goto loc_6a9dc;
...
// 0x44594e4c - DYNL
loc_6ae5b:
if (rax != 0x44594e4c ) goto loc_6b002;
...
// 0x56455253 - VERS
loc_6b731:
if (rax == 0x56455253 ) goto loc_6b81c;经过一些挖掘后,我能够概述metallib文件的结构:
该文件具有88个字节标头,其中包含文件版本,目标平台,图书馆类型,部分索引等。
文件标头中有4个部分:
功能列表
公共元数据
私人元数据
比特代码模块
每个部分都以偏移量和大小记录。这意味着部分可能是无关紧要的,这使Apple可以在两者之间引入新部分而不会破坏兼容性。苹果完全是为“标头扩展”部分做到的 - 它位于功能列表和公共元数据部分之间。
大多数部分(比特码部分除外)类似于基于“标签”的结构:
Fourforkode用作标签的名称/类型。
UInt16 (在大多数情况下)大小的值遵循标签的名称。
源存档数据标签SARC毫不奇怪地使用了其大小的UInt32值 - 源存档很容易超过65kb。
标签分组:
每个组代表项目的一组属性。
标签组以ENDT标签结束。
接下来,我需要弄清每个标签/字段所包含哪些信息。这可能很难从Metal.framework的组装中获得。框架是因为:
某些字段可能纯粹用于工具或调试,因此MTLLibraryDataWithArchive可能会忽略它们。
组件取决于平台。例如, MTLLibraryDataWithArchive的iOS版本只能检查metallib是否是为iOS构建的,并且无法确定是否为MacOS构建了库。
一些领域很难分析和遵循。示例:
该功能的OFFT标签中有3个偏移,它们在哪里指向?他们最终如何使用?
功能类型的可能值是什么?每个值是什么意思?
似乎最快的方法是通过实验。
我首先将metal文件用不同的着色器,选项和SDK编译,然后检查我感兴趣的每个字段。我的桌面很快被metallib文件和Hexfiend Windows淹没了,但我没有找到太多有用的信息。我需要可以自动构建metallib东西,并仅向我展示我感兴趣的字段。
我想到了“测试驱动的猜测”:
基于手头的二进制结构概述编写metallib解析器。
在解析器中,记录当前未知的字段/标签(或某些相关字段)的值。
创建测试,使用不同类型的着色器和可能影响字段价值的选项生成metallib文件,并使用解析器来解析文件数据。
运行测试并分析日志以做出假设。
根据假设更新解析器。
再次运行测试以验证。
经过几轮后,我能够获得功能类型表,目标OS表以及OFFT标签中3个偏移量的含义。
在此过程中,我还发现了一些有趣的事情:
金属不支持WatchOS,但是,可以构建metallib靶向Watchos。苹果确实在WatchOS SDK中包含一些metallib 。 (eg Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreImage.framework/ci_filters.metallib )
空的metallib的靶向iOS旧版本被错误地标记为MACOS。
我无法构建具有目标OS值0x85的metallib 。起初,我认为它可能是为隐藏的真人虫保留的,但后来发现Bridgeos更有可能。
2022年4月10日
LAYR , VATY , CNST等标签包含金属数据类型的UInt8值。可以使用Metal.framework -mtltypeinternal中的私人类检索每个数据类型值的相应描述
id value = [[ NSClassFromString ( @" MTLTypeInternal " ) alloc ] initWithDataType: 0x06 ];
NSLog ( @" %@ " , value.description); // MTLDataTypeFloat4我创建了一个命令行工具来生成金属数据类型表。
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;
}