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;
}