MetalLibraryArchive 는 Apple의 metallib 파일 형식을 리버스 엔지니어링하는 산물입니다.
MetalLibraryArchive 사용하여 metallib 파일에서 라이브러리 유형, 대상 플랫폼, 금속 기능 등을 얻을 수 있습니다.
금속 기능의 추출 된 정보에는 다음이 포함됩니다.
metallib 소스 코드를 포함하도록 구성된 경우. https://yuao.github.io/metallibraryexplorer에서 사용할 수 있습니다
자세히 알아보십시오
"탐색기"라는 실행 가능한 대상이 패키지에 포함되어 있습니다. "Explorer"는 GUI 앱으로, llvm-dis 의 도움으로 열리고 metallib 을 풀고 분해 할 수 있습니다.
참고 llvm-dis 포함되어 있지 않습니다. https://github.com/llvm/llvm-project/releases에서 바이너리 사본을 얻을 수 있습니다.
앱의 "disassembler"메뉴를 사용하여 llvm-dis 실행 파일을 찾으십시오.

MetalLibraryArchive 라이브러리로 사용할 수도 있습니다.
import MetalLibraryArchive
let archive = try Archive ( data : Data ( contentsOf : metallibURL ) )
let libraryType = archive . libraryType
let functions = archive . functions| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
| 0 ... 3 | Fourcharcode | MTLB |
| 4 ... 5 | UINT16 | 대상 플랫폼 |
| 6 ... 9 | (UINT16, UINT16) | Metallib 파일의 버전 (메이저, 마이너) |
| 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) | 비트 코드 섹션의 오프셋 및 크기 |
| 대상 플랫폼 | 값 |
|---|---|
| 마코스 | 0x8001 (0x01,0x80) |
| iOS | 0x0001 (0x01,0x00) |
| 메탈릭 유형 | 값 |
|---|---|
| 실행 파일 | 0x00 |
| 핵심 이미지 | 0x01 |
| 동적 | 0x02 |
| 상징 동반자 | 0x03 |
| 대상 OS | 값 |
|---|---|
| 알려지지 않은 | 0x00 |
| 마코스 | 0x81 |
| iOS | 0x82 |
| TVOS | 0x83 |
| watchos | 0x84 |
| Bridgeos (아마) | 0x85 |
| maccatalyst | 0x86 |
| iOS 시뮬레이터 | 0x87 |
| TVOS 시뮬레이터 | 0x88 |
| Watchos 시뮬레이터 | 0x89 |
| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
| 0 ... 3 | UINT32 | 입력 카운트 (함수 수) |
| 4 ... | 태그 그룹 | 각 태그 그룹에는 금속 기능에 대한 정보가 있습니다. |
태그 그룹의 수는 기능 수와 같습니다.
| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
| 0 ... 3 | UINT32 | 태그 그룹의 크기 |
| 4 ... | 태그 |
| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
| 0 ... 3 | Fourcharcode | 태그의 이름 |
| 4 ... 5 | UINT16 | 태그의 크기 |
| 6 ... | 바이트 | 태그의 내용 |
| 이름 | 컨텐츠 데이터 유형 | 콘텐츠 |
|---|---|---|
| 이름 | 널리 터진 C 스타일 문자열 | 기능의 이름 |
| MDSZ | UINT64 | 비트 코드의 크기 |
| 유형 | UINT8 | 기능의 유형 |
| 해시시 | SHA256 다이제스트 | 비트 코드 데이터의 해시 (SHA256) |
| 오프트 | (uint64, uint64, uint64) | 공개 메타 데이터 섹션, 개인 메타 데이터 섹션 및 비트 코드 섹션 에서이 기능에 대한 정보의 상쇄 |
| 소프 | UINT64 | 임베디드 소스 코드 섹션에서 기능의 소스 코드 아카이브 오프셋 |
| vers | (uint16, uint16, uint16, uint16) | 비트 코드 및 언어 버전 (air.major, air.minor, language.major, language.minor) |
| 평신도 | 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 tag) 및 .air ( DEPF tag) 파일에 대한 경로가 포함되어 있습니다.
FunctionListOffset + FunctionListSize + 4 != PublicMetadataOffset 인 경우에만 존재합니다.
| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
FunctionListOffset + FunctionListSize + 4 ... | 태그 | 헤더 확장 태그 |
| 이름 | 유형 | 콘텐츠 |
|---|---|---|
| hdyn | (uint64, uint64) | 동적 헤더 섹션의 오프셋 및 크기 |
| vlst | (uint64, uint64) | 내보낸 변수 목록의 오프셋 및 크기 |
| 일스트 | (uint64, uint64) | 수입 된 기호 목록의 오프셋 및 크기 |
| HSRD/HSRC | (uint64, uint64) | 임베디드 소스 코드 섹션의 오프셋 및 크기 |
| uuid | uuid | 금속 라이브러리의 uuid. |
| 엔트 | 헤더 확장의 끝 |
| 이름 | 컨텐츠 데이터 유형 | 콘텐츠 |
|---|---|---|
| 이름 | 널리 터진 C 스타일 문자열 | 라이브러리의 이름을 설치하십시오 |
| 다이어 | 널리 터진 C 스타일 문자열 | 링크 된 동적 라이브러리 |
가변 목록 및 가져온 기호 목록에는 기능 목록의 구조와 유사한 구조가 있습니다.
metallib 빌드 프로세스가 소스 코드를 포함하도록 구성된 경우에만 존재합니다.
| 바이트 범위 | 유형 | 콘텐츠 |
|---|---|---|
| 0 ... 1 | UINT16 | 이 섹션의 항목 수 |
| 2 ... n | 널리 터진 C 스타일 문자열 | metallib 파일의 링크 옵션 |
| n ... m | 널리 터진 C 스타일 문자열 | 작업 디렉토리 |
| 중... | 태그 그룹 | 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 | Half3x2 |
| 0x18 | Half3x3 | 0x19 | Half3x4 |
| 0x1a | 반 4x2 | 0x1b | Half4X3 |
| 0x1c | Half4X4 | 0x1d | int |
| 0x1e | int2 | 0x1f | int3 |
| 0x20 | int4 | 0x21 | uint |
| 0x22 | UINT2 | 0x23 | UINT3 |
| 0x24 | UINT4 | 0x25 | 짧은 |
| 0x26 | 짧은 2 | 0x27 | 짧은 3 |
| 0x28 | 짧은 4 | 0x29 | Ushort |
| 0x2A | ushort2 | 0x2b | ushort3 |
| 0x2c | ushort4 | 0x2d | 숯 |
| 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 | IndirectCommandBuffer | 0x51 | 긴 |
| 0x52 | long2 | 0x53 | long3 |
| 0x54 | LONG4 | 0x55 | 울롱 |
| 0x56 | ulong2 | 0x57 | ulong3 |
| 0x58 | ulong4 | 0x59 | 더블 |
| 0x5a | 더블 2 | 0x5b | 더블3 |
| 0x5c | 더블 4 | 0x5d | float8 |
| 0x5e | float16 | 0x5f | 반 8 |
| 0x60 | 반 16 | 0x61 | int8 |
| 0x62 | int16 | 0x63 | UINT8 |
| 0x64 | UINT16 | 0x65 | 짧은 8 |
| 0x66 | 짧은 16 | 0x67 | ushort8 |
| 0x68 | ushort16 | 0x69 | char8 |
| 0x6a | char16 | 0x6b | uchar8 |
| 0x6c | UCHAR16 | 0x6d | Long8 |
| 0x6e | long16 | 0x6f | ulong8 |
| 0x70 | ulong16 | 0x71 | 더블8 |
| 0x72 | 더블 16 | 0x73 | VisibleFunctionTable |
| 0x74 | 교차 기능 | 0x75 | PrimitiveAccelerationstructure |
| 0x76 | Instanceaccelerationstructure | 0x77 | bool8 |
| 0x78 | bool16 |
실수가 있다고 생각되면 문제를여십시오. 실패 테스트를 포함하여 풀 요청을 열도록 선택할 수도 있습니다.
이 프로젝트는 Zhuowei의 연구 없이는 시작되지 않았을 것입니다.이 연구는 metallib 파일의 기본 바이너리 레이아웃, 기능 목록 및 비트 코드 섹션을 밝혀 냈습니다. 감사합니다, @zhuowei!
나는 metallib 파일의 완전한 구조를 얻기 위해 연구를 계속하려고 노력했지만 추측만으로 앞으로 나아가는 것이 너무 어렵다는 것을 알았습니다. 그래서 나는 Framework가 metallib 파일을 어떻게로드하는지 알아 내기 위해 Metal.framework 에 관심을 돌렸다. 다행히도 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은 "헤더 확장"섹션에 대해 정확하게 수행했습니다. 기능 목록과 공개 메타 데이터 섹션 사이에 있습니다.
대부분의 섹션 (비트 코드 섹션 제외)은 "태그"기반 구조와 유사합니다.
Fourcharcode는 태그 이름/유형으로 사용됩니다.
UInt16 (대부분의 경우) 크기의 값은 태그 이름을 따릅니다.
소스 아카이브 데이터 태그 SARC 당연히 크기에 UInt32 값을 사용합니다. 소스 아카이브는 65KB를 쉽게 초과 할 수 있습니다.
태그가 그룹화됩니다.
각 그룹은 항목의 속성 세트를 나타냅니다.
태그 그룹은 ENDT 태그로 끝납니다.
다음으로, 각 태그/필드가 어떤 정보를 보유하는지 알아 내야합니다. 이것은 Metal.framework 조립품에서 얻기가 어려울 수 있습니다.
일부 필드는 순전히 툴링 또는 디버깅을 위해 설계 될 수 있으므로 MTLLibraryDataWithArchive 이를 무시할 수 있습니다.
어셈블리는 플랫폼 의존적입니다. 예를 들어, MTLLibraryDataWithArchive 의 iOS 버전은 metallib iOS 용으로 제작되었는지 여부를 확인할 수 있으며 라이브러리가 MACOS를 위해 제작되었는지 알 수 없습니다.
일부 필드는 분석하고 따르기가 어렵습니다. 예 :
함수의 OFFT 태그에는 3 개의 오프셋이 있으며 어디로 가리키고 있습니까? 그리고 그들은 어떻게 마침내 사용됩니까?
함수 유형의 가능한 값은 무엇입니까? 각 값은 무엇을 의미합니까?
이 정보를 얻는 가장 빠른 방법은 실험을 통한 것 같습니다.
다른 셰이더, 옵션 및 SDK로 metal 파일을 수동으로 컴파일 한 다음 관심있는 각 필드를 검사하는 것으로 시작했습니다. 데스크탑에는 metallib 파일과 Hexfiend Windows가 빠르게 침수되었지만 유용한 정보를 많이 찾지 못했습니다. 나는 자동으로 metallib 만들 수 있고 내가 관심있는 필드 만 제시 할 수있는 것이 필요합니다.
나는 "테스트 중심 추측"을 생각해 냈습니다.
이진 구조 개요를 기반으로 metallib 파서를 작성하십시오.
파서에서 현재 알려지지 않은 필드/태그 (또는 일부 관련 필드)의 값을 기록하십시오.
다양한 종류의 셰이더를 사용하여 metallib 파일을 생성하는 테스트를 생성하고 필드 값에 영향을 줄 수있는 옵션을 컴파일하고 파서를 사용하여 파일 데이터를 구문 분석하십시오.
테스트를 실행하고 로그를 분석하여 가설을 세웁니다.
가설을 기반으로 파서를 업데이트하십시오.
검증하기 위해 테스트를 다시 실행하십시오.
몇 라운드 후에는 기능 유형 테이블, Target OS 테이블 및 OFFT 태그에서 3 개의 오프셋의 의미를 얻을 수있었습니다.
또한이 과정에서 흥미로운 몇 가지를 발견했습니다.
금속은 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 만들 수 없습니다. 처음에 나는 그것이 은폐 된 현실을 위해 예약 될 수 있다고 생각했지만 나중에 Bridgeos의 가능성이 더 높다는 것을 알았습니다.
2022 년 4 월 10 일
LAYR , VATY , CNST 등과 같은 태그에는 금속 데이터 유형의 UInt8 값이 포함되어 있습니다. 각 데이터 유형 값에 대한 해당 설명은 Metal.framework의 개인 클래스를 사용하여 검색 할 수 있습니다.
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;
}