MetalLibraryArchiveリバイスエンジニアリングAppleのmetallibファイル形式の製品です。
MetalLibraryArchiveを使用して、 metallibファイルからライブラリタイプ、ターゲットプラットフォーム、金属機能などを取得できます。
金属関数の抽出された情報には次のものが含まれます。
metallibソースコードを含めるように構成されている場合、関数のソースコード。 https://yuao.github.io/MetallibraryExplorerで入手可能
もっと詳しく知る
「Explorer」と呼ばれる実行可能なターゲットがパッケージに含まれています。 「Explorer」は、( llvm-disの助けを借りて) metallibファイルを開き、開梱し、分解できるGUIアプリです。
注意llvm-dis含まれていません。https://github.com/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 | 4 charcode | mtlb |
| 4 ... 5 | UINT16 | ターゲットプラットフォーム |
| 6 ... 9 | (UINT16、UINT16) | メタリブファイルのバージョン(メジャー、マイナー) |
| 10 | uint8 | メタリブファイルのタイプ |
| 11 | uint8 | ターゲットOS |
| 12 ... 15 | (UINT16、UINT16) | ターゲットOSのバージョン(メジャー、マイナー) |
| 16 ... 23 | UINT64 | メタリブファイルのサイズ |
| 24 ... 39 | (UINT64、UINT64) | 関数リストのオフセットとサイズ |
| 40 ... 55 | (UINT64、UINT64) | パブリックメタデータセクションのオフセットとサイズ |
| 56 ... 71 | (UINT64、UINT64) | プライベートメタデータセクションのオフセットとサイズ |
| 72 ... 87 | (UINT64、UINT64) | ビットコードセクションのオフセットとサイズ |
| ターゲットプラットフォーム | 価値 |
|---|---|
| macos | 0x8001(0x01,0x80) |
| iOS | 0x0001(0x01,0x00) |
| メタリブタイプ | 価値 |
|---|---|
| 実行可能 | 0x00 |
| コア画像 | 0x01 |
| 動的 | 0x02 |
| シンボルコンパニオン | 0x03 |
| ターゲットOS | 価値 |
|---|---|
| 未知 | 0x00 |
| macos | 0x81 |
| iOS | 0x82 |
| TVOS | 0x83 |
| watchos | 0x84 |
| ブリッジス(おそらく) | 0x85 |
| マカタリスト | 0x86 |
| iOSシミュレーター | 0x87 |
| TVOSシミュレーター | 0x88 |
| watchosシミュレーター | 0x89 |
| バイト範囲 | タイプ | コンテンツ |
|---|---|---|
| 0 ... 3 | UINT32 | エントリカウント(関数の数) |
| 4 ... | タググループ | 各タググループは、金属機能に関する情報を保持しています |
タググループの数は、関数の数に等しくなります。
| バイト範囲 | タイプ | コンテンツ |
|---|---|---|
| 0 ... 3 | UINT32 | タググループのサイズ |
| 4 ... | タグ |
| バイト範囲 | タイプ | コンテンツ |
|---|---|---|
| 0 ... 3 | 4 charcode | タグの名前 |
| 4 ... 5 | UINT16 | タグのサイズ |
| 6 ... | バイト | タグのコンテンツ |
| 名前 | コンテンツデータ型 | コンテンツ |
|---|---|---|
| 名前 | ヌル終端cスタイル文字列 | 関数の名前 |
| MDSZ | UINT64 | ビットコードのサイズ |
| タイプ | uint8 | 関数のタイプ |
| ハッシュ | SHA256ダイジェスト | ビットコードデータのハッシュ(SHA256) |
| オフ | (UINT64、UINT64、UINT64) | この関数に関する情報のオフセットパブリックメタデータセクション、プライベートメタデータセクション、およびビットコードセクション |
| ソフ | UINT64 | 埋め込みソースコードセクションの関数のソースコードアーカイブのオフセット |
| 節 | (UINT16、UINT16、UINT16、UINT16) | Bitcode and Languageバージョン(air.major、air.minor、language.major、language.minor) |
| layr | uint8 | render_target_array_indexの金属タイプ(レイヤードレンダリング用) |
| テス | uint8 | パッチタイプとパッチごとの制御ポイントの数(テッセレーション後の頂点関数用) |
| endt | タググループの終わり |
| 関数タイプ | 価値 | 注記 |
|---|---|---|
| 頂点 | 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。 |
| endt | ヘッダー拡張機能の終わり |
| 名前 | コンテンツデータ型 | コンテンツ |
|---|---|---|
| 名前 | ヌル終端cスタイル文字列 | ライブラリの名前をインストールします |
| ダイナル | ヌル終端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 | struct |
| 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 | 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 | 短い |
| 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 | bool3 |
| 0x38 | bool4 | 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 | ComputePipeline |
| 0x50 | directcommandbuffer | 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 | Half8 |
| 0x60 | Half16 | 0x61 | INT8 |
| 0x62 | INT16 | 0x63 | uint8 |
| 0x64 | UINT16 | 0x65 | ショート8 |
| 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 | 交差functionTable | 0x75 | PrimitiveAccelerationStructure |
| 0x76 | InstanceAccelerationStructure | 0x77 | bool8 |
| 0x78 | bool16 |
間違いがあると思われる場合は、問題を開いてください。故障テストを含めて、プルリクエストを開くこともできます。
このプロジェクトは、 metallibファイルの基本的なバイナリレイアウト、関数リスト、およびビットコードセクションを明らかにしたZhuoweiの研究なしでは開始されなかったでしょう。ありがとう、@zhuowei!
metallibファイルの完全な構造を取得するために調査を継続しようとしましたが、当て推量だけに基づいて前進するのは難しすぎることがわかりました。そこで、私はMetal.frameworkに注意を向け、フレームワークがmetallibファイルをどのようにロードするかを見つけたいと考えています。幸いなことに、 Metal.framework/Metalをホッパー分解者にドラッグした後、それほど難しくありません。
Metal.framework MTLLibraryDataWithArchive::parseArchiveSync(...)を使用してmetallibファイルをロードします。 MTLLibraryDataWithArchiveの組み立てには多くの情報が隠されています。例えば:
ファイルは0x424c544d (MTLB)で始まります。ファイルのサイズは、オフセット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);
...
}多くの4ccコード:
// 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は互換性を破ることなく新しいセクションを導入できます。また、Appleは「ヘッダー拡張機能」セクションで正確に行いました。これは、関数リストとパブリックメタデータセクションの間にあります。
ほとんどのセクション(ビットコードセクションを除く)は、「タグ」ベースの構造に似ています。
4 Charcodeは、タグの名前/タイプとして使用されます。
サイズのUInt16 (ほとんどの場合)値は、タグの名前に従います。
ソースアーカイブデータタグSARC 、そのサイズにUInt32値を驚くほど使用していません - ソースアーカイブは65kbを簡単に超えることができます。
タグはグループ化されています:
各グループは、アイテムのプロパティのセットを表します。
タググループは、 ENDTタグで終了します。
次に、各タグ/フィールドがどの情報を保持しているかを把握する必要があります。これは、 Metal.frameworkのアセンブリから取得するのが難しい場合があります。
一部のフィールドは、純粋にツーリングまたはデバッグ用に設計されている場合があるため、 MTLLibraryDataWithArchiveそれらを無視するだけです。
アセンブリはプラットフォームに依存しています。たとえば、 MTLLibraryDataWithArchiveのiOSバージョンは、 metallib iOS用に構築されているかどうかを確認することができ、ライブラリがmacos用に構築されているかどうかを知ることができません。
一部のフィールドは、分析して従うのが難しいです。例:
関数のOFFTには3つのオフセットがありますが、それらはどこに向けていますか?そして、それらは最終的にどのように使用されていますか?
関数タイプの可能な値は何ですか?各価値とはどういう意味ですか?
この情報を取得する最も迅速な方法は、実験を通じてあるようです。
私は、さまざまなシェーダー、オプション、SDKでmetalファイルを手動でコンパイルし、興味のある各フィールドを検査することから始めました。私のデスクトップには、 metallibファイルとヘックスフィードウィンドウがすぐにあふれていましたが、あまり有用な情報が見つかりませんでした。 metallib自動的に構築できるものが必要であり、興味のあるフィールドのみを提供します。
「テスト駆動型の推測」を思いついた:
手元のバイナリ構造の概要に基づいて、 metallibパーサーを書きます。
パーサーでは、現在不明なフィールド/タグ(またはいくつかの関連フィールド)の値を記録します。
さまざまな種類のシェーダーを使用してmetallibファイルを生成するテストを作成し、フィールドの値に影響を与える可能性のあるオプションをコンパイルし、パーサーを使用してファイルデータを解析します。
テストを実行し、ログを分析して仮説を立てます。
仮説に基づいてパーサーを更新します。
検証するためにもう一度テストを実行します。
数ラウンド後、機能型テーブル、ターゲットOSテーブル、およびOFFTタグで3つのオフセットの意味を取得することができました。
また、このプロセスでいくつかの面白いことを見つけました:
MetalはWatchOSをサポートしていませんが、WatchOSをターゲットとするmetallibを構築することは可能です。また、AppleにはWatchos SDKにいくつかのmetallibが含まれています。 ( 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
iOSの古いバージョンをターゲットとする空のmetallibの誤って、macosをターゲットとするものとしてマークされています。
ターゲットOS値0x85を持つmetallibを構築することはできません。最初は、隠されたリアリティのために予約されているかもしれないと思っていましたが、後にブリッジの可能性が高いことがわかりました。
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;
}