FBControlCore/Applications/FBBinaryDescriptor.m (373 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBBinaryDescriptor.h"
#import "FBControlCoreError.h"
#import "FBCollectionInformation.h"
#import "FBControlCoreGlobalConfiguration.h"
#include <mach/machine.h>
#include <stdio.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#import "FBControlCoreError.h"
FBBinaryArchitecture const FBBinaryArchitecturei386 = @"i386";
FBBinaryArchitecture const FBBinaryArchitecturex86_64 = @"x86_64";
FBBinaryArchitecture const FBBinaryArchitectureArm = @"arm";
FBBinaryArchitecture const FBBinaryArchitectureArm64 = @"arm64";
static inline FBBinaryArchitecture ArchitectureForCPUType(cpu_type_t cpuType)
{
NSDictionary<NSNumber *, FBBinaryArchitecture> *lookup = @{
@(CPU_TYPE_I386) : FBBinaryArchitecturei386,
@(CPU_TYPE_X86_64) : FBBinaryArchitecturex86_64,
@(CPU_TYPE_ARM) : FBBinaryArchitectureArm,
@(CPU_TYPE_ARM64) : FBBinaryArchitectureArm64,
};
return lookup[@(cpuType)];
}
static inline NSString *MagicNameForMagic(uint32_t magic)
{
NSDictionary<NSNumber *, NSString *> *lookup = @{
@(MH_MAGIC) : @"MH_MAGIC",
@(MH_CIGAM) : @"MH_CIGAM",
@(MH_MAGIC_64) : @"MH_MAGIC_64",
@(MH_CIGAM_64) : @"MH_CIGAM_64",
@(FAT_MAGIC) : @"FAT_MAGIC",
@(FAT_CIGAM) : @"FAT_CIGAM"
};
return lookup[@(magic)];
}
static inline BOOL IsMagic32(uint32_t magic)
{
return magic == MH_MAGIC || magic == MH_CIGAM;
}
static inline BOOL IsMagic64(uint32_t magic)
{
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
static inline BOOL IsFatMagic(uint32_t magic)
{
return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
static inline BOOL IsSwap(uint32_t magic)
{
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
}
static inline BOOL IsMagic(uint32_t magic)
{
return IsMagic32(magic) || IsMagic64(magic) || IsFatMagic(magic);
}
static inline uint32_t GetMagic(FILE *file)
{
// Get then read from the current position.
long position = ftell(file);
uint32_t magic;
fread(&magic, sizeof(uint32_t), 1, file);
// Move back to the previous position now we know the magic.
fseek(file, position, SEEK_SET);
return magic;
}
static inline struct mach_header ReadHeader32(FILE *file, uint32_t magic)
{
// Read the header from the current location.
struct mach_header header;
fread(&header, sizeof(struct mach_header), 1, file);
if (IsSwap(magic)) {
swap_mach_header(&header, 0);
}
return header;
}
static inline struct mach_header_64 ReadHeader64(FILE *file, uint32_t magic)
{
// Read the header from the current location.
struct mach_header_64 header;
fread(&header, sizeof(header), 1, file);
if (IsSwap(magic)) {
swap_mach_header_64(&header, 0);
}
return header;
}
static inline struct fat_header ReadFatHeader(FILE *file, uint32_t fatMagic)
{
// Get the Fat Header.
struct fat_header header;
fread(&header, sizeof(struct fat_header), 1, file);
if (IsSwap(fatMagic)) {
swap_fat_header(&header, 0);
}
return header;
}
static inline id EnumerateFat(FILE *file, uint32_t fatMagic, id(^block)(struct fat_arch fatArch, uint32_t magic))
{
// Get the Fat Header.
struct fat_header header = ReadFatHeader(file, fatMagic);
long fatArchPosition = sizeof(struct fat_header);
for (uint32_t index = 0; index < header.nfat_arch; index++) {
// Seek-to then get the Fat Arch info
fseek(file, fatArchPosition, SEEK_SET);
struct fat_arch fatArch;
fread(&fatArch, sizeof(struct fat_arch), 1, file);
if (IsSwap(fatMagic)) {
swap_fat_arch(&fatArch, 1, 0);
}
// Seek to the start position of the arch
fseek(file, fatArch.offset, SEEK_SET);
uint32_t magic = GetMagic(file);
if (!IsMagic(magic)){
return nil;
}
// Call the block
id value = block(fatArch, magic);
if (value) {
return value;
}
fatArchPosition += sizeof(struct fat_arch);
}
return nil;
}
static inline id EnumerateLoadCommands64(FILE *file, uint32_t magic, id (^block)(FILE *file, struct load_command command, uint32_t offset) )
{
struct mach_header_64 header = ReadHeader64(file, magic);
// Offset is relative to header length and position in file. In a fat binary it's not right equal to sizeof(header).
uint32_t offset = (uint32_t) ftell(file);
for (uint32_t i = 0; i < header.ncmds; i++) {
struct load_command command = {0x0, 0x0};
fseek(file, offset, SEEK_SET);
fread(&command, sizeof(command), 1, file);
fseek(file, offset, SEEK_SET);
id value = block(file, command, offset);
if (value) {
return value;
}
offset += command.cmdsize;
}
return nil;
}
static inline id EnumerateLoadCommands32(FILE *file, uint32_t magic, id (^block)(FILE *file, struct load_command command, uint32_t offset) )
{
struct mach_header header = ReadHeader32(file, magic);
// Offset is relative to header length and position in file. In a fat binary it's not right equal to sizeof(header).
uint32_t offset = (uint32_t) ftell(file);
for (uint32_t i = 0; i < header.ncmds; i++) {
struct load_command command = {0x0, 0x0};
fseek(file, offset, SEEK_SET);
fread(&command, sizeof(command), 1, file);
fseek(file, offset, SEEK_SET);
id value = block(file, command, offset);
if (value) {
return value;
}
offset += command.cmdsize;
}
return nil;
}
static inline NSArray<NSString *> *ReadRPathsSpecific(FILE *file, uint32_t magic, id(*Enumerator)(FILE *, uint32_t magic, id (^)(FILE *, struct load_command, uint32_t)))
{
NSMutableArray<NSString *> *rpaths = NSMutableArray.array;
Enumerator(file, magic, ^ id (FILE *_, struct load_command command, uint32_t offset) {
if (command.cmd != LC_RPATH) {
return nil;
}
// Offset is calculated from the start of the command
struct rpath_command rpathCommand;
fseek(file, offset, SEEK_SET);
fread(&rpathCommand, sizeof(rpathCommand), 1, file);
// Calculate the offset and move back
const uint32_t pathOffset = offset + rpathCommand.path.offset;
const uint32_t pathLength = command.cmdsize;
// Extract the path
char *path = alloca(command.cmdsize);
fseek(file, pathOffset, SEEK_SET);
fread(path, pathLength, 1, file);
// Create an NSString for the rpaths
NSString *string = [[NSString alloc] initWithBytes:path length:strlen(path) encoding:NSASCIIStringEncoding];
[rpaths addObject:string];
return nil;
});
return rpaths;
}
static inline NSArray<NSString *> *ReadRPaths(FILE *file, uint32_t magic);
static inline NSArray<NSString *> *ReadRPathsFat(FILE *file, uint32_t fatMagic)
{
return EnumerateFat(file, fatMagic, ^id(struct fat_arch fatArch, uint32_t magic) {
return ReadRPaths(file, magic);
});
}
static inline NSUUID *ReadUUID(FILE *file, uint32_t magic);
static id (^UUIDEnumerator)(FILE *, struct load_command, uint32_t) = ^id (FILE *file, struct load_command command, uint32_t offset){
if (command.cmd != LC_UUID) {
return nil;
}
struct uuid_command uuidCommand;
fread(&uuidCommand, sizeof(uuidCommand), 1, file);
NSUUID *uuid = [[NSUUID alloc] initWithUUIDBytes:uuidCommand.uuid];
return uuid;
};
static inline NSUUID *ReadUUID64(FILE *file, uint32_t magic)
{
return EnumerateLoadCommands64(file, magic, UUIDEnumerator);
}
static inline NSUUID *ReadUUID32(FILE *file, uint32_t magic)
{
return EnumerateLoadCommands32(file, magic, UUIDEnumerator);
}
static inline NSUUID *ReadUUIDFat(FILE *file, uint32_t fatMagic)
{
return EnumerateFat(file, fatMagic, ^ id (struct fat_arch fatArch, uint32_t magic) {
// Get the Arch
return ReadUUID(file, magic);
});
}
static inline NSUUID *ReadUUID(FILE *file, uint32_t magic)
{
if (IsFatMagic(magic)) {
return ReadUUIDFat(file, magic);
}
if (IsMagic64(magic)) {
return ReadUUID64(file, magic);
}
if (IsMagic32(magic)) {
return ReadUUID32(file, magic);
}
return nil;
}
static inline FBBinaryArchitecture ReadArch32(FILE *file, uint32_t magic)
{
struct mach_header header = ReadHeader32(file, magic);
return ArchitectureForCPUType(header.cputype);
}
static inline FBBinaryArchitecture ReadArch64(FILE *file, uint32_t magic)
{
struct mach_header_64 header = ReadHeader64(file, magic);
return ArchitectureForCPUType(header.cputype);
}
static inline FBBinaryArchitecture ReadArch(FILE *file, uint32_t magic)
{
if (IsMagic32(magic)) {
return ReadArch32(file, magic);
}
if (IsMagic64(magic)) {
return ReadArch64(file, magic);
}
return nil;
}
static inline NSArray<FBBinaryArchitecture> *ReadArchsFat(FILE *file, uint32_t fatMagic)
{
NSMutableArray<FBBinaryArchitecture> *array = [NSMutableArray array];
EnumerateFat(file, fatMagic, ^id(struct fat_arch fatArch, uint32_t magic) {
// Get the Arch
NSString *arch = ReadArch(file, magic);
if (!arch) {
return nil;
}
[array addObject:arch];
return nil;
});
return array;
}
static inline NSArray<FBBinaryArchitecture> *ReadArchs(FILE *file, uint32_t magic)
{
if (IsFatMagic(magic)) {
return ReadArchsFat(file, magic);
}
if (IsMagic32(magic)) {
FBBinaryArchitecture arch = ReadArch32(file, magic);
return arch ? @[arch] : @[];
}
if (IsMagic64(magic)) {
FBBinaryArchitecture arch = ReadArch64(file, magic);
return arch ? @[arch] : @[];
}
return @[];
}
static inline NSArray<NSString *> *ReadRPaths(FILE *file, uint32_t magic)
{
if (IsFatMagic(magic)) {
return ReadRPathsFat(file, magic);
}
if (IsMagic64(magic)) {
return ReadRPathsSpecific(file, magic, EnumerateLoadCommands64);
}
if (IsMagic32(magic)) {
return ReadRPathsSpecific(file, magic, EnumerateLoadCommands32);
}
return nil;
}
@implementation FBBinaryDescriptor
- (instancetype)initWithName:(NSString *)name architectures:(NSSet<FBBinaryArchitecture> *)architectures uuid:(NSUUID *)uuid path:(NSString *)path
{
NSParameterAssert(name);
NSParameterAssert(architectures);
NSParameterAssert(path);
self = [super init];
if (!self) {
return nil;
}
_name = name;
_architectures = architectures;
_uuid = uuid;
_path = path;
return self;
}
+ (nullable instancetype)binaryWithPath:(NSString *)binaryPath error:(NSError **)error;
{
if (![NSFileManager.defaultManager fileExistsAtPath:binaryPath]) {
return [[FBControlCoreError
describeFormat:@"Binary does not exist at path %@", binaryPath]
fail:error];
}
FILE *file = fopen(binaryPath.UTF8String, "rb");
if (file == NULL) {
return [[FBControlCoreError describeFormat:@"Could not fopen file at path %@", binaryPath] fail:error];
}
// Seek to and read the magic.
rewind(file);
uint32_t magic = GetMagic(file);
if (!IsMagic(magic)) {
fclose(file);
return [[FBControlCoreError describeFormat:@"Could not interpret magic '%d' in file %@", magic, binaryPath] fail:error];
}
NSArray *archs = ReadArchs(file, magic);
if (!archs) {
fclose(file);
return [[FBControlCoreError describeFormat:@"Could not read architechtures of magic %@ in file %@", MagicNameForMagic(magic), binaryPath] fail:error];
}
// Rewind to the start of the file
rewind(file);
NSUUID *uuid = ReadUUID(file, magic);
fclose(file);
return [[FBBinaryDescriptor alloc]
initWithName:[self binaryNameForBinaryPath:binaryPath]
architectures:[NSSet setWithArray:archs]
uuid:uuid
path:binaryPath];
}
#pragma mark NSCopying
- (instancetype)copyWithZone:(NSZone *)zone
{
// Is immutable.
return self;
}
#pragma mark NSObject
- (BOOL)isEqual:(FBBinaryDescriptor *)object
{
if (![object isMemberOfClass:self.class]) {
return NO;
}
return [object.name isEqual:self.name] &&
[object.path isEqual:self.path] &&
[object.architectures isEqual:self.architectures];
}
- (NSUInteger)hash
{
return self.name.hash | self.path.hash | self.architectures.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:
@"Name: %@ | Path: %@ | Architectures: %@",
self.name,
self.path,
[FBCollectionInformation oneLineDescriptionFromArray:self.architectures.allObjects]
];
}
#pragma mark Public Methods
- (NSArray<NSString *> *)rpathsWithError:(NSError **)error
{
FILE *file = fopen(self.path.UTF8String, "rb");
if (file == NULL) {
return [[FBControlCoreError describeFormat:@"Could not fopen file at path %@", self.path] fail:error];
}
// Seek to and read the magic.
rewind(file);
uint32_t magic = GetMagic(file);
if (!IsMagic(magic)) {
fclose(file);
return [[FBControlCoreError describeFormat:@"Could not interpret magic '%d' in file %@", magic, self.path] fail:error];
}
NSArray<NSString *> *rpaths = ReadRPaths(file, magic);
fclose(file);
return rpaths;
}
#pragma mark Private
+ (NSString *)binaryNameForBinaryPath:(NSString *)binaryPath
{
return binaryPath.lastPathComponent;
}
@end