2016-04-21 15:57:02 +03:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Net ;
2019-03-01 10:00:53 +03:00
using System.Text ;
2020-02-03 23:19:27 +03:00
using Xamarin.Bundler ;
2016-04-21 15:57:02 +03:00
2022-12-07 20:07:53 +03:00
#nullable enable
2017-07-11 09:54:50 +03:00
#if MLAUNCH
using Xamarin.Launcher ;
2019-05-03 16:18:03 +03:00
#elif XAMARIN_HOSTING
using Xamarin.Hosting ;
2017-07-11 09:54:50 +03:00
#endif
2016-04-21 15:57:02 +03:00
2022-09-28 17:25:35 +03:00
namespace Xamarin {
2018-03-01 18:36:06 +03:00
[Flags]
public enum Abi {
2022-09-28 17:25:35 +03:00
None = 0 ,
i386 = 1 ,
ARMv6 = 2 ,
ARMv7 = 4 ,
ARMv7s = 8 ,
ARM64 = 16 ,
x86_64 = 32 ,
Thumb = 64 ,
LLVM = 128 ,
2018-03-01 18:36:06 +03:00
ARMv7k = 256 ,
2018-12-11 19:50:20 +03:00
ARM64e = 512 ,
ARM64_32 = 1024 ,
2018-03-01 18:36:06 +03:00
SimulatorArchMask = i386 | x86_64 ,
2018-12-11 19:50:20 +03:00
DeviceArchMask = ARMv6 | ARMv7 | ARMv7s | ARMv7k | ARM64 | ARM64e | ARM64_32 ,
2018-03-01 18:36:06 +03:00
ArchMask = SimulatorArchMask | DeviceArchMask ,
2018-12-11 19:50:20 +03:00
Arch64Mask = x86_64 | ARM64 | ARM64e ,
Arch32Mask = i386 | ARMv6 | ARMv7 | ARMv7s | ARMv7k | ARM64_32 /* This is a 32-bit arch for our purposes */ ,
2018-03-01 18:36:06 +03:00
}
2020-05-11 17:27:19 +03:00
public static class AbiExtensions {
public static string AsString ( this Abi self )
{
var rv = ( self & Abi . ArchMask ) . ToString ( ) ;
if ( ( self & Abi . LLVM ) = = Abi . LLVM )
rv + = "+LLVM" ;
if ( ( self & Abi . Thumb ) = = Abi . Thumb )
rv + = "+Thumb" ;
return rv ;
}
public static string AsArchString ( this Abi self )
{
return ( self & Abi . ArchMask ) . ToString ( ) . ToLowerInvariant ( ) ;
}
}
2022-09-28 17:25:35 +03:00
public class MachO {
2016-04-21 15:57:02 +03:00
/* definitions from: /usr/include/mach-o/loader.h */
/* Constant for the magic field of the mach_header (32-bit architectures) */
internal const uint MH_MAGIC = 0xfeedface ; /* the mach magic number */
internal const uint MH_CIGAM = 0xcefaedfe ; /* NXSwapInt(MH_MAGIC) */
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
internal const uint MH_MAGIC_64 = 0xfeedfacf ; /* the 64-bit mach magic number */
internal const uint MH_CIGAM_64 = 0xcffaedfe ; /* NXSwapInt(MH_MAGIC_64) */
/* definitions from: /usr/include/mach-o/fat.h */
internal const uint FAT_MAGIC = 0xcafebabe ;
internal const uint FAT_CIGAM = 0xbebafeca ; /* NXSwapLong(FAT_MAGIC) */
internal const uint MH_DYLIB = 0x6 ; /* dynamically bound shared library */
2018-12-11 19:50:20 +03:00
// Values here match the corresponding values in the Abi enum.
2016-04-21 15:57:02 +03:00
public enum Architectures {
2022-09-28 17:25:35 +03:00
None = 0 ,
i386 = 1 ,
ARMv6 = 2 ,
ARMv7 = 4 ,
ARMv7s = 8 ,
ARM64 = 16 ,
x86_64 = 32 ,
2018-12-11 19:50:20 +03:00
ARMv7k = 256 ,
ARM64e = 512 ,
ARM64_32 = 1024 ,
2016-04-21 15:57:02 +03:00
}
2022-09-28 17:25:35 +03:00
public enum LoadCommands : uint {
2016-04-21 15:57:02 +03:00
//#define LC_REQ_DYLD 0x80000000
ReqDyld = 0x80000000 ,
/ /
// /* Constants for the cmd field of all load commands, the type */
//#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
//#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
//#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
//#define LC_THREAD 0x4 /* thread */
//#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
//#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
//#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
//#define LC_IDENT 0x8 /* object identification info (obsolete) */
//#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
//#define LC_PREPAGE 0xa /* prepage command (internal use) */
//#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
//#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
LoadDylib = 0xc ,
//#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
IdDylib = 0xd ,
//#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
//#define LC_ID_DYLINKER 0xf /* dynamic linker identification */
//#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
// /* linked shared library */
//#define LC_ROUTINES 0x11 /* image routines */
//#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
//#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
//#define LC_SUB_CLIENT 0x14 /* sub client */
//#define LC_SUB_LIBRARY 0x15 /* sub library */
//#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
//#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
/ /
// /*
// * load a dynamically linked shared library that is allowed to be missing
// * (all symbols are weak imported).
// */
//#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
LoadWeakDylib = 0x18 | ReqDyld ,
/ /
//#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be
// mapped */
//#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
//#define LC_UUID 0x1b /* the uuid */
2016-06-28 21:35:05 +03:00
Uuid = 0x1b ,
2016-04-21 15:57:02 +03:00
//#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */
//#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
2022-09-21 12:38:03 +03:00
CodeSignature = 0x1d ,
2016-04-21 15:57:02 +03:00
//#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
//#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
ReexportDylib = 0x1f | ReqDyld ,
//#define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */
//#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */
//#define LC_DYLD_INFO 0x22 /* compressed dyld information */
//#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */
//#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
2019-02-06 11:48:42 +03:00
MinMacOSX = 0x24 , //#define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */
MiniPhoneOS = 0x25 , //#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
2022-09-28 17:25:35 +03:00
//#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
//#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat
// like environment variable */
//#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
//#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
//#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
//#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
//#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
//#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
//#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
2019-02-06 11:48:42 +03:00
MintvOS = 0x2f , //#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
MinwatchOS = 0x30 , //#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
2022-09-28 17:25:35 +03:00
//#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
2019-03-01 10:00:53 +03:00
BuildVersion = 0x32 , //#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */
}
2019-02-06 11:48:42 +03:00
2019-03-01 10:00:53 +03:00
public enum Platform : uint {
MacOS = 1 ,
IOS = 2 ,
TvOS = 3 ,
WatchOS = 4 ,
BridgeOS = 5 ,
IOSSimulator = 7 ,
TvOSSimulator = 8 ,
WatchOSSimulator = 9 ,
2016-04-21 15:57:02 +03:00
}
internal static uint FromBigEndian ( uint number )
{
return ( ( ( number > > 24 ) & 0xFF )
| ( ( number > > 08 ) & 0xFF00 )
| ( ( number < < 08 ) & 0xFF0000 )
| ( ( number < < 24 ) ) ) ;
}
internal static int FromBigEndian ( int number )
{
return ( ( ( number > > 24 ) & 0xFF )
| ( ( number > > 08 ) & 0xFF00 )
| ( ( number < < 08 ) & 0xFF0000 )
| ( ( number < < 24 ) ) ) ;
}
internal static uint ToBigEndian ( uint number )
{
return ( ( ( number > > 24 ) & 0xFF )
| ( ( number > > 08 ) & 0xFF00 )
| ( ( number < < 08 ) & 0xFF0000 )
| ( ( number < < 24 ) ) ) ;
}
internal static int ToBigEndian ( int number )
{
return ( ( ( number > > 24 ) & 0xFF )
| ( ( number > > 08 ) & 0xFF00 )
| ( ( number < < 08 ) & 0xFF0000 )
| ( ( number < < 24 ) ) ) ;
}
static object ReadFile ( BinaryReader reader , string filename )
{
var magic = reader . ReadUInt32 ( ) ;
reader . BaseStream . Position = 0 ;
switch ( magic ) {
case MH_MAGIC :
case MH_MAGIC_64 :
var mf = new MachOFile ( filename ) ;
mf . Read ( reader ) ;
return mf ;
case FAT_MAGIC : // little-endian fat binary
case FAT_CIGAM : // big-endian fat binary
{
2022-09-28 17:25:35 +03:00
var f = new FatFile ( filename ) ;
f . Read ( reader ) ;
return f ;
}
2016-04-21 15:57:02 +03:00
default :
if ( StaticLibrary . IsStaticLibrary ( reader ) ) {
var sl = new StaticLibrary ( ) ;
2019-03-01 10:00:53 +03:00
sl . Read ( filename , reader , reader . BaseStream . Length ) ;
2016-04-21 15:57:02 +03:00
return sl ;
}
throw new Exception ( string . Format ( "File format not recognized: {0} (magic: 0x{1})" , filename , magic . ToString ( "X" ) ) ) ;
}
}
static object ReadFile ( string filename )
{
using ( var fs = new FileStream ( filename , FileMode . Open , FileAccess . Read , FileShare . Read ) ) {
using ( var reader = new BinaryReader ( fs ) ) {
return ReadFile ( reader , filename ) ;
}
}
}
public static IEnumerable < MachOFile > Read ( string filename )
{
var file = ReadFile ( filename ) ;
var fatfile = file as FatFile ;
2022-12-07 20:07:53 +03:00
if ( fatfile is not null ) {
foreach ( var ff in fatfile . entries ! ) {
if ( ff . entry is not null )
2019-03-01 10:00:53 +03:00
yield return ff . entry ;
2022-12-07 20:07:53 +03:00
if ( ff . static_library is not null )
2019-03-01 10:00:53 +03:00
foreach ( var obj in ff . static_library . ObjectFiles )
yield return obj ;
}
2016-04-21 15:57:02 +03:00
} else {
var mf = file as MachOFile ;
2022-12-07 20:07:53 +03:00
if ( mf is not null ) {
2016-04-21 15:57:02 +03:00
yield return mf ;
2019-09-24 14:40:18 +03:00
yield break ;
}
var sl = file as StaticLibrary ;
2022-12-07 20:07:53 +03:00
if ( sl is not null ) {
2019-09-24 14:40:18 +03:00
foreach ( var obj in sl . ObjectFiles )
yield return obj ;
yield break ;
}
2022-09-28 17:25:35 +03:00
2020-01-31 23:02:52 +03:00
throw ErrorHelper . CreateError ( 1604 , Errors . MX1604 , file . GetType ( ) . Name , filename ) ;
2016-04-21 15:57:02 +03:00
}
}
#if MTOUCH
// Removes all architectures from the target file, except for those in 'architectures'.
// This method doesn't do anything if the target file is a thin mach-o file.
// Also it doesn't do anything if the result is an empty file (i.e. none of the
// selected architectures match any of the architectures in the file) - this is
// only because I haven't investigated what would be needed elsewhere in the link
// process when the entire file is removed. FIXME <--.
public static void SelectArchitectures ( string filename , ICollection < Abi > abis )
{
var architectures = GetArchitectures ( abis ) ;
var tmpfile = filename + ".tmp" ;
if ( abis . Count = = 0 )
return ;
using ( var fsr = new FileStream ( filename , FileMode . Open , FileAccess . Read ) ) {
using ( var reader = new BinaryReader ( fsr ) ) {
var file = ReadFile ( filename ) ;
if ( file is MachOFile ) {
Driver . Log ( 2 , "Skipping architecture selecting of '{0}', since it only contains 1 architecture." , filename ) ;
return ;
}
var fatfile = ( FatFile ) file ;
bool any_removed = false ;
// remove architectures we don't want
2022-12-07 20:07:53 +03:00
for ( int i = fatfile . entries ! . Count - 1 ; i > = 0 ; i - - ) {
2016-04-21 15:57:02 +03:00
var ff = fatfile . entries [ i ] ;
2022-12-07 20:07:53 +03:00
if ( ! architectures . Contains ( ff . entry ! . Architecture ) ) {
2016-04-21 15:57:02 +03:00
any_removed = true ;
fatfile . entries . RemoveAt ( i ) ;
fatfile . nfat_arch - - ;
Driver . Log ( 2 , "Removing architecture {0} from {1}" , ff . entry . Architecture , filename ) ;
}
}
if ( ! any_removed ) {
Driver . Log ( 2 , "Architecture selection of '{0}' didn't find any architectures to remove." , filename ) ;
return ;
}
if ( fatfile . nfat_arch = = 0 ) {
Driver . Log ( 2 , "Skipping architecture selection of '{0}', none of the selected architectures match any of the architectures in the archive." , filename , architectures [ 0 ] ) ;
return ;
}
if ( fatfile . nfat_arch = = 1 ) {
// Thin file
var entry = fatfile . entries [ 0 ] ;
using ( var fsw = new FileStream ( tmpfile , FileMode . Create , FileAccess . Write ) ) {
using ( var writer = new BinaryWriter ( fsw ) ) {
entry . WriteFile ( writer , reader , entry . offset ) ;
}
}
} else {
// Fat file
// Re-calculate header data
var read_offset = new List < uint > ( fatfile . entries . Count ) ;
read_offset . Add ( fatfile . entries [ 0 ] . offset ) ;
fatfile . entries [ 0 ] . offset = ( uint ) ( 1 < < ( int ) fatfile . entries [ 0 ] . align ) ;
for ( int i = 1 ; i < fatfile . entries . Count ; i + + ) {
read_offset . Add ( fatfile . entries [ i ] . offset ) ;
fatfile . entries [ i ] . offset = fatfile . entries [ i - 1 ] . offset + fatfile . entries [ i - 1 ] . size ;
var alignSize = ( 1 < < ( int ) fatfile . entries [ i ] . align ) ;
var align = ( int ) fatfile . entries [ i ] . offset % alignSize ;
if ( align ! = 0 )
fatfile . entries [ i ] . offset + = ( uint ) ( alignSize - align ) ;
}
// Write out the fat file
using ( var fsw = new FileStream ( tmpfile , FileMode . Create , FileAccess . Write ) ) {
using ( var writer = new BinaryWriter ( fsw ) ) {
// write headers
fatfile . WriteHeaders ( writer ) ;
// write data
for ( int i = 0 ; i < fatfile . entries . Count ; i + + ) {
fatfile . entries [ i ] . Write ( writer , reader , read_offset [ i ] ) ;
}
}
}
}
}
}
File . Delete ( filename ) ;
File . Move ( tmpfile , filename ) ;
}
#endif
static Dictionary < string , IEnumerable < string > > native_dependencies = new Dictionary < string , IEnumerable < string > > ( ) ;
public static IEnumerable < string > GetNativeDependencies ( string libraryName )
{
2022-12-07 20:07:53 +03:00
IEnumerable < string > ? result ;
2016-04-21 15:57:02 +03:00
lock ( native_dependencies ) {
if ( native_dependencies . TryGetValue ( libraryName , out result ) )
return result ;
}
var macho_files = Read ( libraryName ) ;
var dependencies = new HashSet < string > ( ) ;
foreach ( var macho_file in macho_files ) {
foreach ( var lc in macho_file . load_commands ) {
var dyld_lc = lc as Xamarin . DylibLoadCommand ;
2022-12-07 20:07:53 +03:00
if ( dyld_lc ? . name is not null ) {
2016-04-21 15:57:02 +03:00
dependencies . Add ( dyld_lc . name ) ;
}
}
}
result = dependencies ;
lock ( native_dependencies )
native_dependencies . Add ( libraryName , result ) ;
return result ;
}
public static List < Abi > GetArchitectures ( string file )
{
var result = new List < Abi > ( ) ;
// https://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
using ( var fs = File . OpenRead ( file ) ) {
using ( var reader = new BinaryReader ( fs ) ) {
int magic = reader . ReadInt32 ( ) ;
int architectures ;
switch ( ( uint ) magic ) {
case 0xCAFEBABE : // little-endian fat binary
architectures = reader . ReadInt32 ( ) ;
for ( int i = 0 ; i < architectures ; i + + ) {
result . Add ( GetArch ( reader . ReadInt32 ( ) , reader . ReadInt32 ( ) ) ) ;
// skip to next entry
reader . ReadInt32 ( ) ; // offset
reader . ReadInt32 ( ) ; // size
reader . ReadInt32 ( ) ; // align
}
break ;
case 0xBEBAFECA :
architectures = System . Net . IPAddress . NetworkToHostOrder ( reader . ReadInt32 ( ) ) ;
for ( int i = 0 ; i < architectures ; i + + ) {
result . Add ( GetArch ( System . Net . IPAddress . NetworkToHostOrder ( reader . ReadInt32 ( ) ) , System . Net . IPAddress . NetworkToHostOrder ( reader . ReadInt32 ( ) ) ) ) ;
// skip to next entry
reader . ReadInt32 ( ) ; // offset
reader . ReadInt32 ( ) ; // size
reader . ReadInt32 ( ) ; // align
}
break ;
case 0xFEEDFACE : // little-endian mach-o header
case 0xFEEDFACF : // little-endian 64-big mach-o header
result . Add ( GetArch ( reader . ReadInt32 ( ) , reader . ReadInt32 ( ) ) ) ;
break ;
case 0xCFFAEDFE :
case 0xCEFAEDFE :
result . Add ( GetArch ( System . Net . IPAddress . NetworkToHostOrder ( reader . ReadInt32 ( ) ) , System . Net . IPAddress . NetworkToHostOrder ( reader . ReadInt32 ( ) ) ) ) ;
break ;
default :
Console . WriteLine ( "File '{0}' is neither a Universal binary nor a Mach-O binary (magic: 0x{1})" , file , magic . ToString ( "x" ) ) ;
break ;
}
}
}
return result ;
}
public static List < Architectures > GetArchitectures ( ICollection < Abi > abi )
{
var rv = new List < Architectures > ( abi . Count ) ;
foreach ( var a in abi ) {
rv . Add ( ( Architectures ) ( a & Abi . ArchMask ) ) ;
}
return rv ;
}
2018-12-11 19:50:20 +03:00
public static Abi GetArch ( int cputype , int cpusubtype )
2016-04-21 15:57:02 +03:00
{
switch ( cputype ) {
case 12 : // arm
switch ( cpusubtype ) {
case 6 :
return Abi . ARMv6 ;
case 9 :
return Abi . ARMv7 ;
case 11 :
return Abi . ARMv7s ;
2017-02-20 18:03:41 +03:00
case 12 :
return Abi . ARMv7k ;
2016-04-21 15:57:02 +03:00
default :
return Abi . None ;
}
case 12 | 0x01000000 :
2018-12-11 19:50:20 +03:00
switch ( cpusubtype ) {
case 2 :
return Abi . ARM64e ;
case 0 :
default :
return Abi . ARM64 ;
}
case 12 | 0x02000000 : // CPU_TYPE_ARM | CPU_ARCH_ABI64_32 (64-bit hardware with 32-bit types; LP32)
switch ( cpusubtype ) {
case 1 : // CPU_SUBTYPE_ARM64_32_V8
return Abi . ARM64_32 ;
default :
return Abi . None ;
}
2016-04-21 15:57:02 +03:00
case 7 : // x86
return Abi . i386 ;
case 7 | 0x01000000 : // x64
return Abi . x86_64 ;
}
return Abi . None ;
}
public static bool IsDynamicFramework ( string filename )
{
var f = ReadFile ( filename ) ;
if ( f is StaticLibrary )
return false ;
else if ( f is MachOFile )
return ( ( MachOFile ) f ) . IsDynamicLibrary ;
var fat = f as FatFile ;
2022-12-07 20:07:53 +03:00
if ( fat is null )
2016-04-21 15:57:02 +03:00
return false ;
2022-12-07 20:07:53 +03:00
foreach ( var entry in fat . entries ! )
2016-04-21 15:57:02 +03:00
if ( ! entry . IsDynamicLibrary )
return false ;
2022-09-28 17:25:35 +03:00
2016-04-21 15:57:02 +03:00
return true ;
}
2021-05-21 23:29:08 +03:00
public static bool IsMachOFile ( string filename )
{
2021-06-24 12:40:26 +03:00
using ( var fs = File . OpenRead ( filename ) ) {
2021-05-21 23:29:08 +03:00
if ( fs . Length < 4 )
return false ;
using ( var reader = new BinaryReader ( fs ) ) {
var magic = reader . ReadUInt32 ( ) ;
switch ( magic ) {
case MH_MAGIC :
case MH_MAGIC_64 :
case FAT_MAGIC : // little-endian fat binary
case FAT_CIGAM : // big-endian fat binary
return true ;
default :
return false ;
}
}
}
}
2016-04-21 15:57:02 +03:00
}
2022-09-28 17:25:35 +03:00
public class StaticLibrary {
2019-03-01 10:00:53 +03:00
List < MachOFile > object_files = new List < MachOFile > ( ) ;
public IEnumerable < MachOFile > ObjectFiles { get { return object_files ; } }
static string ReadString ( BinaryReader reader , int length )
{
var bytes = reader . ReadBytes ( length ) ;
for ( var i = 0 ; i < bytes . Length ; i + + ) {
if ( bytes [ i ] = = 0 ) {
length = i ;
break ;
}
}
return Encoding . ASCII . GetString ( bytes , 0 , length ) ;
}
static long ReadDecimal ( BinaryReader reader , int length )
{
var str = ReadString ( reader , length ) ;
str = str . TrimEnd ( ' ' ) ;
return long . Parse ( str ) ;
}
static long ReadOctal ( BinaryReader reader , int length )
{
var str = ReadString ( reader , length ) ;
str = str . TrimEnd ( ' ' ) ;
return Convert . ToInt64 ( str , 8 ) ;
}
internal void Read ( string filename , BinaryReader reader , long size )
2016-04-21 15:57:02 +03:00
{
IsStaticLibrary ( reader , throw_if_error : true ) ;
2019-03-01 10:00:53 +03:00
var pos = reader . BaseStream . Position ;
reader . BaseStream . Position + = 8 ; // header
byte [ ] bytes ;
while ( reader . BaseStream . Position < pos + size ) {
var fileIdentifier = ReadString ( reader , 16 ) ;
var fileModificationTimestamp = ReadDecimal ( reader , 12 ) ;
var ownerId = ReadDecimal ( reader , 6 ) ;
var groupId = ReadDecimal ( reader , 6 ) ;
var fileMode = ReadOctal ( reader , 8 ) ;
var fileSize = ReadDecimal ( reader , 10 ) ;
bytes = reader . ReadBytes ( 2 ) ; // ending characters
if ( bytes [ 0 ] ! = 0x60 & & bytes [ 1 ] ! = 0x0A )
2022-09-28 17:25:35 +03:00
throw ErrorHelper . CreateError ( 1605 , Errors . MT1605 , fileIdentifier , filename , bytes [ 0 ] . ToString ( "x" ) , bytes [ 1 ] . ToString ( "x" ) ) ;
2019-03-01 10:00:53 +03:00
if ( fileIdentifier . StartsWith ( "#1/" , StringComparison . Ordinal ) ) {
var nameLength = int . Parse ( fileIdentifier . Substring ( 3 ) . TrimEnd ( ' ' ) ) ;
fileIdentifier = ReadString ( reader , nameLength ) ;
fileSize - = nameLength ;
}
var nextPosition = reader . BaseStream . Position + fileSize ;
if ( MachOFile . IsMachOLibrary ( null , reader ) ) {
var file = new MachOFile ( fileIdentifier ) ;
file . Read ( reader ) ;
object_files . Add ( file ) ;
}
// byte position is always even after each file.
if ( nextPosition % 1 = = 1 )
nextPosition + + ;
reader . BaseStream . Position = nextPosition ;
}
2016-04-21 15:57:02 +03:00
}
public static bool IsStaticLibrary ( BinaryReader reader , bool throw_if_error = false )
{
var pos = reader . BaseStream . Position ;
var bytes = reader . ReadBytes ( 8 ) ;
2021-05-21 23:29:08 +03:00
bool rv ;
if ( bytes . Length < 8 ) {
rv = false ;
} else {
rv = bytes [ 0 ] = = '!' & & bytes [ 1 ] = = '<' & & bytes [ 2 ] = = 'a' & & bytes [ 3 ] = = 'r' & & bytes [ 4 ] = = 'c' & & bytes [ 5 ] = = 'h' & & bytes [ 6 ] = = '>' & & bytes [ 7 ] = = 0xa ;
}
2016-04-21 15:57:02 +03:00
reader . BaseStream . Position = pos ;
if ( throw_if_error & & ! rv )
2020-01-31 23:02:52 +03:00
throw ErrorHelper . CreateError ( 1601 , Errors . MT1601 , System . Text . Encoding . ASCII . GetString ( bytes , 0 , 7 ) ) ;
2016-04-21 15:57:02 +03:00
return rv ;
}
2021-05-21 23:29:08 +03:00
public static bool IsStaticLibrary ( string filename , bool throw_if_error = false )
{
2021-06-24 12:40:26 +03:00
using ( var fs = File . OpenRead ( filename ) ) {
2021-05-21 23:29:08 +03:00
using ( var reader = new BinaryReader ( fs ) ) {
return IsStaticLibrary ( reader , throw_if_error ) ;
}
}
}
2016-04-21 15:57:02 +03:00
}
2022-09-28 17:25:35 +03:00
public class MachOFile {
2022-12-07 20:07:53 +03:00
FatEntry ? fat_parent ;
string? filename ;
2016-04-21 15:57:02 +03:00
public uint magic ;
int _cputype ;
int _cpusubtype ;
uint _filetype ;
uint _ncmds ;
uint _sizeofcmds ;
uint _flags ;
uint _reserved ;
bool is64bitheader ;
public int cputype { get { return is_big_endian ? MachO . ToBigEndian ( _cputype ) : _cputype ; } }
public int cpusubtype { get { return is_big_endian ? MachO . ToBigEndian ( _cpusubtype ) : _cpusubtype ; } }
public uint filetype { get { return is_big_endian ? MachO . ToBigEndian ( _filetype ) : _filetype ; } }
public uint ncmds { get { return is_big_endian ? MachO . ToBigEndian ( _ncmds ) : _ncmds ; } }
public uint sizeofcmds { get { return is_big_endian ? MachO . ToBigEndian ( _sizeofcmds ) : _sizeofcmds ; } }
public uint flags { get { return is_big_endian ? MachO . ToBigEndian ( _flags ) : _flags ; } }
public uint reserved { get { return is_big_endian ? MachO . ToBigEndian ( _reserved ) : _reserved ; } }
2022-12-07 20:07:53 +03:00
public List < LoadCommand > load_commands = new List < LoadCommand > ( ) ;
2016-04-21 15:57:02 +03:00
2022-12-07 20:07:53 +03:00
public string? Filename { get { return filename ; } }
public FatEntry ? Parent { get { return fat_parent ; } }
2019-03-01 10:00:53 +03:00
2016-04-21 15:57:02 +03:00
public MachOFile ( FatEntry parent )
{
fat_parent = parent ;
}
public MachOFile ( string filename )
{
this . filename = filename ;
}
internal bool is_big_endian {
get {
return magic = = MachO . FAT_CIGAM | | magic = = MachO . MH_CIGAM_64 ;
}
}
internal void WriteHeader ( BinaryWriter writer )
{
writer . Write ( magic ) ;
writer . Write ( _cputype ) ;
writer . Write ( _cpusubtype ) ;
writer . Write ( _filetype ) ;
writer . Write ( _ncmds ) ;
writer . Write ( _sizeofcmds ) ;
writer . Write ( _flags ) ;
if ( is64bitheader )
writer . Write ( reserved ) ;
}
2022-12-07 20:07:53 +03:00
internal static bool IsMachOLibrary ( FatEntry ? fat_entry , BinaryReader reader , bool throw_if_error = false )
2016-04-21 15:57:02 +03:00
{
var pos = reader . BaseStream . Position ;
var magic = reader . ReadUInt32 ( ) ;
var rv = false ;
switch ( magic ) {
case MachO . MH_CIGAM :
case MachO . MH_MAGIC :
case MachO . MH_CIGAM_64 :
case MachO . MH_MAGIC_64 :
rv = true ;
break ;
default :
rv = false ;
break ;
}
reader . BaseStream . Position = pos ;
if ( throw_if_error & & ! rv )
2022-12-07 20:07:53 +03:00
throw ErrorHelper . CreateError ( 1600 , Errors . MX1600 , magic . ToString ( "x" ) , fat_entry ? . Parent ? . Filename ) ;
2016-04-21 15:57:02 +03:00
return rv ;
}
internal void Read ( BinaryReader reader )
{
/* definitions from: /usr/include/mach-o/loader.h */
/ *
* The 32 - bit mach header appears at the very beginning of the object file for
* 32 - bit architectures .
* /
2022-09-28 17:25:35 +03:00
// struct mach_header {
// uint32_t magic; /* mach magic number identifier */
// cpu_type_t cputype; /* cpu specifier */
// cpu_subtype_t cpusubtype; /* machine specifier */
// uint32_t filetype; /* type of file */
// uint32_t ncmds; /* number of load commands */
// uint32_t sizeofcmds; /* the size of all the load commands */
// uint32_t flags; /* flags */
// };
/ *
* The 64 - bit mach header appears at the very beginning of object files for
* 64 - bit architectures .
* /
// struct mach_header_64 {
// uint32_t magic; /* mach magic number identifier */
// cpu_type_t cputype; /* cpu specifier */
// cpu_subtype_t cpusubtype; /* machine specifier */
// uint32_t filetype; /* type of file */
// uint32_t ncmds; /* number of load commands */
// uint32_t sizeofcmds; /* the size of all the load commands */
// uint32_t flags; /* flags */
// uint32_t reserved; /* reserved */
// };
2016-04-21 15:57:02 +03:00
magic = reader . ReadUInt32 ( ) ;
switch ( magic ) {
case MachO . MH_CIGAM :
case MachO . MH_MAGIC :
is64bitheader = false ;
break ;
case MachO . MH_CIGAM_64 :
case MachO . MH_MAGIC_64 :
is64bitheader = true ;
break ;
default :
2022-12-07 20:07:53 +03:00
throw ErrorHelper . CreateError ( 1602 , Errors . MX1602 , magic . ToString ( "x" ) , fat_parent ? . Parent ? . Filename ? ? filename ) ;
2016-04-21 15:57:02 +03:00
}
_cputype = reader . ReadInt32 ( ) ;
_cpusubtype = reader . ReadInt32 ( ) ;
_filetype = reader . ReadUInt32 ( ) ;
_ncmds = reader . ReadUInt32 ( ) ;
_sizeofcmds = reader . ReadUInt32 ( ) ;
_flags = reader . ReadUInt32 ( ) ;
if ( is64bitheader )
_reserved = reader . ReadUInt32 ( ) ;
var cmds = new List < LoadCommand > ( ( int ) ncmds ) ;
for ( int i = 0 ; i < ncmds ; i + + ) {
var cmd = ( MachO . LoadCommands ) reader . ReadUInt32 ( ) ;
reader . BaseStream . Position - = 4 ;
LoadCommand lc ;
switch ( cmd ) {
case MachO . LoadCommands . LoadDylib :
case MachO . LoadCommands . LoadWeakDylib :
2022-09-28 17:25:35 +03:00
case MachO . LoadCommands . ReexportDylib : {
2016-04-21 15:57:02 +03:00
var dlc = new DylibLoadCommand ( ) ;
dlc . cmd = reader . ReadUInt32 ( ) ;
dlc . cmdsize = reader . ReadUInt32 ( ) ;
2022-09-28 17:25:35 +03:00
/*var nameofs = */
reader . ReadUInt32 ( ) ;
2016-04-21 15:57:02 +03:00
dlc . timestamp = reader . ReadUInt32 ( ) ;
dlc . current_version = reader . ReadUInt32 ( ) ;
dlc . compatibility_version = reader . ReadUInt32 ( ) ;
var namelength = dlc . cmdsize - 6 * 4 ;
var namechars = reader . ReadBytes ( ( int ) namelength ) ;
// strip off any null characters at the end.
for ( int n = namechars . Length - 1 ; n > = 0 ; n - - ) {
if ( namechars [ n ] = = 0 )
namelength - - ;
else
break ;
}
dlc . name = System . Text . UTF8Encoding . UTF8 . GetString ( namechars , 0 , ( int ) namelength ) ;
lc = dlc ;
break ;
2022-02-16 23:13:40 +03:00
}
case MachO . LoadCommands . IdDylib : {
var dlc = new DylibIdCommand ( ) ;
dlc . cmd = reader . ReadUInt32 ( ) ;
dlc . cmdsize = reader . ReadUInt32 ( ) ;
2022-09-28 17:25:35 +03:00
/*var nameofs = */
reader . ReadUInt32 ( ) ;
2022-02-16 23:13:40 +03:00
dlc . timestamp = reader . ReadUInt32 ( ) ;
dlc . current_version = reader . ReadUInt32 ( ) ;
dlc . compatibility_version = reader . ReadUInt32 ( ) ;
var namelength = dlc . cmdsize - 6 * 4 ;
var namechars = reader . ReadBytes ( ( int ) namelength ) ;
// strip off any null characters at the end.
for ( int n = namechars . Length - 1 ; n > = 0 ; n - - ) {
if ( namechars [ n ] = = 0 )
namelength - - ;
else
break ;
}
dlc . name = Encoding . UTF8 . GetString ( namechars , 0 , ( int ) namelength ) ;
lc = dlc ;
break ;
}
2016-06-28 21:35:05 +03:00
case MachO . LoadCommands . Uuid :
var uuidCmd = new UuidCommand ( ) ;
uuidCmd . cmd = reader . ReadUInt32 ( ) ;
uuidCmd . cmdsize = reader . ReadUInt32 ( ) ;
uuidCmd . uuid = reader . ReadBytes ( 16 ) ; // defined in the header as uint8_t uuid [16]
lc = uuidCmd ;
break ;
2019-02-06 11:48:42 +03:00
case MachO . LoadCommands . MintvOS :
case MachO . LoadCommands . MinMacOSX :
case MachO . LoadCommands . MiniPhoneOS :
case MachO . LoadCommands . MinwatchOS :
var minCmd = new MinCommand ( ) ;
minCmd . cmd = reader . ReadUInt32 ( ) ;
minCmd . cmdsize = reader . ReadUInt32 ( ) ;
minCmd . version = reader . ReadUInt32 ( ) ;
minCmd . sdk = reader . ReadUInt32 ( ) ;
lc = minCmd ;
break ;
2019-03-01 10:00:53 +03:00
case MachO . LoadCommands . BuildVersion :
var buildVer = new BuildVersionCommand ( ) ;
buildVer . cmd = reader . ReadUInt32 ( ) ;
buildVer . cmdsize = reader . ReadUInt32 ( ) ;
buildVer . platform = reader . ReadUInt32 ( ) ;
buildVer . minos = reader . ReadUInt32 ( ) ;
buildVer . sdk = reader . ReadUInt32 ( ) ;
buildVer . ntools = reader . ReadUInt32 ( ) ;
2022-09-28 17:25:35 +03:00
buildVer . tools = new BuildVersionCommand . BuildToolVersion [ buildVer . ntools ] ;
2019-03-01 10:00:53 +03:00
for ( int j = 0 ; j < buildVer . ntools ; j + + ) {
var buildToolVer = new BuildVersionCommand . BuildToolVersion ( ) ;
buildToolVer . tool = reader . ReadUInt32 ( ) ;
buildToolVer . version = reader . ReadUInt32 ( ) ;
2022-09-28 17:25:35 +03:00
buildVer . tools [ j ] = buildToolVer ;
2019-03-01 10:00:53 +03:00
}
lc = buildVer ;
break ;
2016-04-21 15:57:02 +03:00
default :
lc = new LoadCommand ( ) ;
lc . cmd = reader . ReadUInt32 ( ) ;
lc . cmdsize = reader . ReadUInt32 ( ) ;
reader . BaseStream . Position + = lc . cmdsize - 8 ;
break ;
}
cmds . Add ( lc ) ;
}
load_commands = cmds ;
}
public MachO . Architectures Architecture {
get {
2018-12-11 19:50:20 +03:00
return ( MachO . Architectures ) MachO . GetArch ( cputype , cpusubtype ) ;
2016-04-21 15:57:02 +03:00
}
}
public bool IsDynamicLibrary {
get { return filetype = = MachO . MH_DYLIB ; }
}
}
public class FatFile {
public readonly string Filename ;
public uint magic ;
uint _nfat_arch ;
public FatFile ( string filename )
{
Filename = filename ;
}
public uint nfat_arch {
get { return is_big_endian ? MachO . ToBigEndian ( _nfat_arch ) : _nfat_arch ; }
set { _nfat_arch = is_big_endian ? MachO . FromBigEndian ( value ) : value ; }
}
2022-12-07 20:07:53 +03:00
public List < FatEntry > ? entries ;
2016-04-21 15:57:02 +03:00
internal bool is_big_endian {
2022-09-28 17:25:35 +03:00
get { return magic = = MachO . FAT_CIGAM ; }
2016-04-21 15:57:02 +03:00
}
internal void WriteHeader ( BinaryWriter writer )
{
writer . Write ( magic ) ;
writer . Write ( _nfat_arch ) ;
}
internal void WriteHeaders ( BinaryWriter writer )
{
WriteHeader ( writer ) ;
2022-12-07 20:07:53 +03:00
for ( int i = 0 ; i < entries ! . Count ; i + + ) {
2016-04-21 15:57:02 +03:00
entries [ i ] . WriteHeader ( writer ) ;
}
}
internal void Read ( BinaryReader reader )
{
magic = reader . ReadUInt32 ( ) ;
_nfat_arch = reader . ReadUInt32 ( ) ;
entries = new List < FatEntry > ( ( int ) nfat_arch ) ;
for ( int i = 0 ; i < ( int ) nfat_arch ; i + + ) {
var entry = new FatEntry ( ) ;
entry . Read ( this , reader ) ;
entries . Add ( entry ) ;
}
foreach ( var entry in entries )
entry . ReadEntry ( reader ) ;
}
}
public class FatEntry {
2022-12-07 20:07:53 +03:00
FatFile ? parent ;
2016-04-21 15:57:02 +03:00
public int cputype ;
public int cpusubtype ;
public uint offset ;
public uint size ;
public uint align ;
2022-12-07 20:07:53 +03:00
public MachOFile ? entry ;
public StaticLibrary ? static_library ;
2022-09-28 17:25:35 +03:00
2022-12-07 20:07:53 +03:00
public bool IsDynamicLibrary { get { return entry ? . IsDynamicLibrary = = true ; } }
public FatFile Parent { get { return parent ! ; } }
2016-04-21 15:57:02 +03:00
internal void WriteHeader ( BinaryWriter writer )
{
2022-12-07 20:07:53 +03:00
if ( Parent . is_big_endian ) {
2016-04-21 15:57:02 +03:00
writer . Write ( MachO . ToBigEndian ( cputype ) ) ;
writer . Write ( MachO . ToBigEndian ( cpusubtype ) ) ;
writer . Write ( MachO . ToBigEndian ( offset ) ) ;
writer . Write ( MachO . ToBigEndian ( size ) ) ;
writer . Write ( MachO . ToBigEndian ( align ) ) ;
} else {
writer . Write ( cputype ) ;
writer . Write ( cpusubtype ) ;
writer . Write ( offset ) ;
writer . Write ( size ) ;
writer . Write ( align ) ;
}
}
2022-09-28 17:25:35 +03:00
2016-04-21 15:57:02 +03:00
internal void Write ( BinaryWriter writer , BinaryReader reader , uint reader_offset )
{
writer . BaseStream . Position = offset ;
// write data
WriteFile ( writer , reader , reader_offset ) ;
}
internal void WriteFile ( BinaryWriter writer , BinaryReader reader , uint reader_offset )
{
// write data
var ofs = writer . BaseStream . Position ;
reader . BaseStream . Position = reader_offset ;
var buffer = new byte [ 1 < < ( int ) align ] ;
var left = ( int ) size ;
while ( left > 0 ) {
var read = reader . Read ( buffer , 0 , Math . Min ( buffer . Length , left ) ) ;
writer . Write ( buffer , 0 , read ) ;
left - = read ;
}
writer . BaseStream . Position = ofs ; // restore to the post-header location.
}
internal void Read ( FatFile parent , BinaryReader reader )
{
this . parent = parent ;
cputype = reader . ReadInt32 ( ) ;
cpusubtype = reader . ReadInt32 ( ) ;
offset = reader . ReadUInt32 ( ) ;
size = reader . ReadUInt32 ( ) ;
align = reader . ReadUInt32 ( ) ;
if ( parent . is_big_endian ) {
cputype = MachO . FromBigEndian ( cputype ) ;
cpusubtype = MachO . FromBigEndian ( cpusubtype ) ;
offset = MachO . FromBigEndian ( offset ) ;
size = MachO . FromBigEndian ( size ) ;
align = MachO . FromBigEndian ( align ) ;
}
}
internal void ReadEntry ( BinaryReader reader )
{
reader . BaseStream . Position = offset ;
if ( MachOFile . IsMachOLibrary ( this , reader ) ) {
entry = new MachOFile ( this ) ;
entry . Read ( reader ) ;
} else if ( StaticLibrary . IsStaticLibrary ( reader ) ) {
static_library = new StaticLibrary ( ) ;
2022-12-07 20:07:53 +03:00
static_library . Read ( parent ? . Filename ! , reader , size ) ;
2016-04-21 15:57:02 +03:00
} else {
2022-12-07 20:07:53 +03:00
throw ErrorHelper . CreateError ( 1603 , Errors . MX1603 , offset , parent ? . Filename ) ;
2016-04-21 15:57:02 +03:00
}
}
}
public class LoadCommand {
public uint cmd ;
public uint cmdsize ;
2019-02-06 11:48:42 +03:00
public MachO . LoadCommands Command {
get { return ( MachO . LoadCommands ) cmd ; }
}
2016-04-21 15:57:02 +03:00
#if DEBUG
public virtual void Dump ( )
{
Console . WriteLine ( " cmd: {0}" , cmd ) ;
Console . WriteLine ( " cmdsize: {0}" , cmdsize ) ;
}
#endif
}
public class DylibLoadCommand : LoadCommand {
2022-12-07 20:07:53 +03:00
public string name = string . Empty ;
2016-04-21 15:57:02 +03:00
public uint timestamp ;
public uint current_version ;
public uint compatibility_version ;
#if DEBUG
public override void Dump ( )
{
base . Dump ( ) ;
Console . WriteLine ( " name: {0}" , name ) ;
Console . WriteLine ( " timestamp: {0}" , timestamp ) ;
Console . WriteLine ( " current_version: {0}" , current_version ) ;
Console . WriteLine ( " compatibility_version: {0}" , compatibility_version ) ;
}
2016-06-28 21:35:05 +03:00
#endif
}
2022-09-28 17:25:35 +03:00
2022-02-16 23:13:40 +03:00
public class DylibIdCommand : LoadCommand {
2022-12-07 20:07:53 +03:00
public string name = string . Empty ;
2022-02-16 23:13:40 +03:00
public uint timestamp ;
public uint current_version ;
public uint compatibility_version ;
#if DEBUG
public override void Dump ( )
{
base . Dump ( ) ;
Console . WriteLine ( " name: {0}" , name ) ;
Console . WriteLine ( " timestamp: {0}" , timestamp ) ;
Console . WriteLine ( " current_version: {0}" , current_version ) ;
Console . WriteLine ( " compatibility_version: {0}" , compatibility_version ) ;
}
#endif
}
2016-06-28 21:35:05 +03:00
public class UuidCommand : LoadCommand {
2022-12-07 20:07:53 +03:00
public byte [ ] ? uuid ;
2016-06-28 21:35:05 +03:00
#if DEBUG
public override void Dump ( )
{
base . Dump ( ) ;
Console . WriteLine ( " cmd: {0}" , cmd ) ;
Console . WriteLine ( " uuid: {0}" , uuid ) ;
}
2016-04-21 15:57:02 +03:00
#endif
}
2019-02-06 11:48:42 +03:00
public class MinCommand : LoadCommand {
public uint version ; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
public uint sdk ; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
Version DeNibble ( uint value )
{
2022-09-28 17:25:35 +03:00
return new Version ( ( int ) ( value > > 16 ) , ( int ) ( ( value > > 8 ) & 0xFF ) , ( int ) ( value & 0xFF ) ) ;
2019-02-06 11:48:42 +03:00
}
public Version Version {
get { return DeNibble ( version ) ; }
}
public Version Sdk {
get { return DeNibble ( sdk ) ; }
}
}
2019-03-01 10:00:53 +03:00
public class BuildVersionCommand : LoadCommand {
public uint platform ;
public uint minos ; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
public uint sdk ; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
public uint ntools ;
2022-12-07 20:07:53 +03:00
public BuildToolVersion [ ] ? tools ;
2019-03-01 10:00:53 +03:00
public class BuildToolVersion {
public uint tool ;
public uint version ;
}
Version DeNibble ( uint value )
{
2022-09-28 17:25:35 +03:00
return new Version ( ( int ) ( value > > 16 ) , ( int ) ( ( value > > 8 ) & 0xFF ) , ( int ) ( value & 0xFF ) ) ;
2019-03-01 10:00:53 +03:00
}
public Version MinOS {
get { return DeNibble ( minos ) ; }
}
public Version Sdk {
get { return DeNibble ( sdk ) ; }
}
public MachO . Platform Platform {
2022-09-28 17:25:35 +03:00
get { return ( MachO . Platform ) platform ; }
2019-03-01 10:00:53 +03:00
}
}
2016-04-21 15:57:02 +03:00
}