From 01d642f150c90ac8a6e7ec266f8c424a8f197bc4 Mon Sep 17 00:00:00 2001 From: Martin Afanasjew Date: Thu, 18 Feb 2016 18:04:41 +0100 Subject: [PATCH] vendor/macho: update to 0.2.2-39-ge2fbedc9 --- Library/Homebrew/vendor/README.md | 2 +- .../Homebrew/vendor/macho/macho/exceptions.rb | 32 +++++- .../Homebrew/vendor/macho/macho/fat_file.rb | 60 ++++++++-- .../Homebrew/vendor/macho/macho/headers.rb | 16 ++- .../vendor/macho/macho/load_commands.rb | 2 +- .../Homebrew/vendor/macho/macho/macho_file.rb | 103 ++++++------------ Library/Homebrew/vendor/macho/macho/tools.rb | 6 +- 7 files changed, 130 insertions(+), 91 deletions(-) diff --git a/Library/Homebrew/vendor/README.md b/Library/Homebrew/vendor/README.md index 325bab28d2..ce1178f168 100644 --- a/Library/Homebrew/vendor/README.md +++ b/Library/Homebrew/vendor/README.md @@ -3,7 +3,7 @@ Vendored Dependencies * [okjson](https://github.com/kr/okjson), version 43. -* [ruby-macho](https://github.com/woodruffw/ruby-macho), version 0.2.2. +* [ruby-macho](https://github.com/Homebrew/ruby-macho), version 0.2.2-39-ge2fbedc9. ## Licenses: diff --git a/Library/Homebrew/vendor/macho/macho/exceptions.rb b/Library/Homebrew/vendor/macho/macho/exceptions.rb index 1295176b2d..07ae43edb7 100644 --- a/Library/Homebrew/vendor/macho/macho/exceptions.rb +++ b/Library/Homebrew/vendor/macho/macho/exceptions.rb @@ -3,14 +3,36 @@ module MachO class MachOError < RuntimeError end + # Raised when a file is not a Mach-O. + class NotAMachOError < MachOError + # @param error [String] the error in question + def initialize(error) + super error + end + end + + # Raised when a file is too short to be a valid Mach-O file. + class TruncatedFileError < NotAMachOError + def initialize + super "File is too short to be a valid Mach-O" + end + end + # Raised when a file's magic bytes are not valid Mach-O magic. - class MagicError < MachOError + class MagicError < NotAMachOError # @param num [Fixnum] the unknown number def initialize(num) super "Unrecognized Mach-O magic: 0x#{"%02x" % num}" end end + # Raised when a file is a Java classfile instead of a fat Mach-O. + class JavaClassFileError < NotAMachOError + def initialize + super "File is a Java class file" + end + end + # Raised when a fat binary is loaded with MachOFile. class FatBinaryError < MachOError def initialize @@ -82,4 +104,12 @@ module MachO super "No such runtime path: #{path}" end end + + # Raised whenever unfinished code is called. + class UnimplementedError < MachOError + # @param thing [String] the thing that is unimplemented + def initialize(thing) + super "Unimplemented: #{thing}" + end + end end diff --git a/Library/Homebrew/vendor/macho/macho/fat_file.rb b/Library/Homebrew/vendor/macho/macho/fat_file.rb index 829ee83f0e..0b25483a9f 100644 --- a/Library/Homebrew/vendor/macho/macho/fat_file.rb +++ b/Library/Homebrew/vendor/macho/macho/fat_file.rb @@ -13,6 +13,16 @@ module MachO # @return [Array] an array of Mach-O binaries attr_reader :machos + # Creates a new FatFile instance from a binary string. + # @param bin [String] a binary string containing raw Mach-O data + # @return [MachO::FatFile] a new FatFile + def self.new_from_bin(bin) + instance = allocate + instance.initialize_from_bin(bin) + + instance + end + # Creates a new FatFile from the given filename. # @param filename [String] the fat file to load from # @raise [ArgumentError] if the given filename does not exist @@ -26,6 +36,15 @@ module MachO @machos = get_machos end + # @api private + def initialize_from_bin(bin) + @filename = nil + @raw_data = bin + @header = get_fat_header + @fat_archs = get_fat_archs + @machos = get_machos + end + # The file's raw fat data. # @return [String] the raw fat data def serialize @@ -131,8 +150,10 @@ module MachO # All shared libraries linked to the file's Mach-Os. # @return [Array] an array of all shared libraries def linked_dylibs - # can machos inside fat binaries have different dylibs? - machos.flat_map(&:linked_dylibs).uniq + # Individual architectures in a fat binary can link to different subsets + # of libraries, but at this point we want to have the full picture, i.e. + # the union of all libraries used by all architectures. + machos.map(&:linked_dylibs).flatten.uniq end # Changes all dependent shared library install names from `old_name` to `new_name`. @@ -168,25 +189,45 @@ module MachO end # Write all (fat) data to the file used to initialize the instance. + # @return [void] + # @raise [MachO::MachOError] if the instance was initialized without a file # @note Overwrites all data in the file! def write! - File.open(@filename, "wb") { |f| f.write(@raw_data) } + if filename.nil? + raise MachOError.new("cannot write to a default file when initialized from a binary string") + else + File.open(@filename, "wb") { |f| f.write(@raw_data) } + end end private # Obtain the fat header from raw file data. # @return [MachO::FatHeader] the fat header + # @raise [MachO::TruncatedFileError] if the file is too small to have a valid header # @raise [MachO::MagicError] if the magic is not valid Mach-O magic # @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file + # @raise [MachO::JavaClassFileError] if the file is a Java classfile # @private def get_fat_header - magic, nfat_arch = @raw_data[0..7].unpack("N2") + # the smallest fat Mach-O header is 8 bytes + raise TruncatedFileError.new if @raw_data.size < 8 - raise MagicError.new(magic) unless MachO.magic?(magic) - raise MachOBinaryError.new unless MachO.fat_magic?(magic) + fh = FatHeader.new_from_bin(@raw_data[0, FatHeader.bytesize]) - FatHeader.new(magic, nfat_arch) + raise MagicError.new(fh.magic) unless MachO.magic?(fh.magic) + raise MachOBinaryError.new unless MachO.fat_magic?(fh.magic) + + # Rationale: Java classfiles have the same magic as big-endian fat + # Mach-Os. Classfiles encode their version at the same offset as + # `nfat_arch` and the lowest version number is 43, so we error out + # if a file claims to have over 30 internal architectures. It's + # technically possible for a fat Mach-O to have over 30 architectures, + # but this is extremely unlikely and in practice distinguishes the two + # formats. + raise JavaClassFileError.new if fh.nfat_arch > 30 + + fh end # Obtain an array of fat architectures from raw file data. @@ -195,9 +236,10 @@ module MachO def get_fat_archs archs = [] + fa_off = FatHeader.bytesize + fa_len = FatArch.bytesize header.nfat_arch.times do |i| - fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5") - archs << FatArch.new(*fields) + archs << FatArch.new_from_bin(@raw_data[fa_off + (fa_len * i), fa_len]) end archs diff --git a/Library/Homebrew/vendor/macho/macho/headers.rb b/Library/Homebrew/vendor/macho/macho/headers.rb index 7e4d126a37..1f99fe549f 100644 --- a/Library/Homebrew/vendor/macho/macho/headers.rb +++ b/Library/Homebrew/vendor/macho/macho/headers.rb @@ -33,14 +33,11 @@ module MachO # any CPU (unused?) CPU_TYPE_ANY = -1 - # x86 compatible CPUs - CPU_TYPE_X86 = 0x07 - # i386 and later compatible CPUs - CPU_TYPE_I386 = CPU_TYPE_X86 + CPU_TYPE_I386 = 0x07 # x86_64 (AMD64) compatible CPUs - CPU_TYPE_X86_64 = (CPU_TYPE_X86 | CPU_ARCH_ABI64) + CPU_TYPE_X86_64 = (CPU_TYPE_I386 | CPU_ARCH_ABI64) # PowerPC compatible CPUs (7400 series?) CPU_TYPE_POWERPC = 0x12 @@ -51,7 +48,6 @@ module MachO # association of cpu types to string representations CPU_TYPES = { CPU_TYPE_ANY => "CPU_TYPE_ANY", - CPU_TYPE_X86 => "CPU_TYPE_X86", CPU_TYPE_I386 => "CPU_TYPE_I386", CPU_TYPE_X86_64 => "CPU_TYPE_X86_64", CPU_TYPE_POWERPC => "CPU_TYPE_POWERPC", @@ -166,7 +162,7 @@ module MachO # @return [Fixnum] the number of fat architecture structures following the header attr_reader :nfat_arch - FORMAT = "VV" + FORMAT = "N2" # always big-endian SIZEOF = 8 # @api private @@ -195,7 +191,7 @@ module MachO # @return [Fixnum] the alignment, as a power of 2 attr_reader :align - FORMAT = "VVVVV" + FORMAT = "N5" # always big-endian SIZEOF = 20 # @api private @@ -239,7 +235,9 @@ module MachO flags) @magic = magic @cputype = cputype - @cpusubtype = cpusubtype + # For now we're not interested in additional capability bits also to be + # found in the `cpusubtype` field. We only care about the CPU sub-type. + @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK @filetype = filetype @ncmds = ncmds @sizeofcmds = sizeofcmds diff --git a/Library/Homebrew/vendor/macho/macho/load_commands.rb b/Library/Homebrew/vendor/macho/macho/load_commands.rb index e5c71b1608..5d30d8ddae 100644 --- a/Library/Homebrew/vendor/macho/macho/load_commands.rb +++ b/Library/Homebrew/vendor/macho/macho/load_commands.rb @@ -830,7 +830,7 @@ module MachO SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid) + def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad) super(raw_data, offset, cmd, cmdsize) @cryptoff = cryptoff @cryptsize = cryptsize diff --git a/Library/Homebrew/vendor/macho/macho/macho_file.rb b/Library/Homebrew/vendor/macho/macho/macho_file.rb index 39d584c8e9..dfdf4ae2cb 100644 --- a/Library/Homebrew/vendor/macho/macho/macho_file.rb +++ b/Library/Homebrew/vendor/macho/macho/macho_file.rb @@ -215,7 +215,11 @@ module MachO # All shared libraries linked to the Mach-O. # @return [Array] an array of all shared libraries def linked_dylibs - dylib_load_commands.map(&:name).map(&:to_s) + # Some linkers produce multiple `LC_LOAD_DYLIB` load commands for the same + # library, but at this point we're really only interested in a list of + # unique libraries this Mach-O file links to, thus: `uniq`. (This is also + # for consistency with `FatFile` that merges this list across all archs.) + dylib_load_commands.map(&:name).map(&:to_s).uniq end # Changes the shared library `old_name` to `new_name` @@ -293,7 +297,6 @@ module MachO end # Write all Mach-O data to the file used to initialize the instance. - # @raise [MachOError] if the instance was created from a binary string # @return [void] # @raise [MachO::MachOError] if the instance was initialized without a file # @note Overwrites all data in the file! @@ -310,30 +313,29 @@ module MachO # The file's Mach-O header structure. # @return [MachO::MachHeader] if the Mach-O is 32-bit # @return [MachO::MachHeader64] if the Mach-O is 64-bit + # @raise [MachO::TruncatedFileError] if the file is too small to have a valid header # @private def get_mach_header - magic = get_magic - cputype = get_cputype - cpusubtype = get_cpusubtype - filetype = get_filetype - ncmds = get_ncmds - sizeofcmds = get_sizeofcmds - flags = get_flags + # the smallest Mach-O header is 28 bytes + raise TruncatedFileError.new if @raw_data.size < 28 - if MachO.magic32?(magic) - MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags) - else - # the reserved field is...reserved, so just fill it with 0 - MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0) - end + magic = get_and_check_magic + mh_klass = MachO.magic32?(magic) ? MachHeader : MachHeader64 + mh = mh_klass.new_from_bin(@raw_data[0, mh_klass.bytesize]) + + check_cputype(mh.cputype) + check_cpusubtype(mh.cpusubtype) + check_filetype(mh.filetype) + + mh end - # The file's magic number. + # Read just the file's magic number and check its validity. # @return [Fixnum] the magic # @raise [MachO::MagicError] if the magic is not valid Mach-O magic # @raise [MachO::FatBinaryError] if the magic is for a Fat file # @private - def get_magic + def get_and_check_magic magic = @raw_data[0..3].unpack("N").first raise MagicError.new(magic) unless MachO.magic?(magic) @@ -342,62 +344,29 @@ module MachO magic end - # The file's CPU type. - # @return [Fixnum] the CPU type + # Check the file's CPU type. + # @param cputype [Fixnum] the CPU type # @raise [MachO::CPUTypeError] if the CPU type is unknown # @private - def get_cputype - cputype = @raw_data[4..7].unpack("V").first - + def check_cputype(cputype) raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype) - - cputype end - # The file's CPU subtype. - # @return [Fixnum] the CPU subtype - # @raise [MachO::CPUSubtypeError] if the CPU subtype is unknown + # Check the file's CPU sub-type. + # @param cpusubtype [Fixnum] the CPU subtype + # @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown # @private - def get_cpusubtype - cpusubtype = @raw_data[8..11].unpack("V").first - cpusubtype &= ~CPU_SUBTYPE_LIB64 # this mask isn't documented! - + def check_cpusubtype(cpusubtype) + # Only check sub-type w/o capability bits (see `get_mach_header`). raise CPUSubtypeError.new(cpusubtype) unless CPU_SUBTYPES.key?(cpusubtype) - - cpusubtype end - # The file's type. - # @return [Fixnum] the file type + # Check the file's type. + # @param filetype [Fixnum] the file type # @raise [MachO::FiletypeError] if the file type is unknown # @private - def get_filetype - filetype = @raw_data[12..15].unpack("V").first - + def check_filetype(filetype) raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype) - - filetype - end - - # The number of load commands in the file. - # @return [Fixnum] the number of load commands - # @private - def get_ncmds - @raw_data[16..19].unpack("V").first - end - - # The size of all load commands, in bytes. - # return [Fixnum] the size of all load commands - # @private - def get_sizeofcmds - @raw_data[20..23].unpack("V").first - end - - # The Mach-O header's flags. - # @return [Fixnum] the flags - # @private - def get_flags - @raw_data[24..27].unpack("V").first end # All load commands in the file. @@ -484,17 +453,17 @@ module MachO new_size = cmd.class.bytesize + new_str.size new_sizeofcmds += new_size - cmd.cmdsize - low_fileoff = 2**64 # ULLONGMAX + low_fileoff = @raw_data.size # calculate the low file offset (offset to first section data) segments.each do |seg| sections(seg).each do |sect| - if sect.size != 0 && !sect.flag?(:S_ZEROFILL) && - !sect.flag?(:S_THREAD_LOCAL_ZEROFILL) && - sect.offset < low_fileoff + next if sect.size == 0 + next if sect.flag?(:S_ZEROFILL) + next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL) + next unless sect.offset < low_fileoff - low_fileoff = sect.offset - end + low_fileoff = sect.offset end end diff --git a/Library/Homebrew/vendor/macho/macho/tools.rb b/Library/Homebrew/vendor/macho/macho/tools.rb index 18f20fb802..fe3da455b4 100644 --- a/Library/Homebrew/vendor/macho/macho/tools.rb +++ b/Library/Homebrew/vendor/macho/macho/tools.rb @@ -41,7 +41,7 @@ module MachO # @return [void] # @todo unstub def self.change_rpath(filename, old_path, new_path) - raise "stub" + raise UnimplementedError.new("changing rpaths in a Mach-O") end # Add a runtime path to a Mach-O or Fat binary, overwriting the source file. @@ -50,7 +50,7 @@ module MachO # @return [void] # @todo unstub def self.add_rpath(filename, new_path) - raise "stub" + raise UnimplementedError.new("adding rpaths to a Mach-O") end # Delete a runtime path from a Mach-O or Fat binary, overwriting the source file. @@ -59,7 +59,7 @@ module MachO # @return [void] # @todo unstub def self.delete_rpath(filename, old_path) - raise "stub" + raise UnimplementedError.new("removing rpaths from a Mach-O") end end end