From 5caa812e2cc762f0af772a51ccf24b9524dea7a5 Mon Sep 17 00:00:00 2001 From: William Woodruff <william@tuffbizz.com> Date: Sun, 7 Aug 2016 13:36:19 -0400 Subject: [PATCH] vendor: Update vendored ruby-macho to 0.2.5. --- Library/Homebrew/vendor/README.md | 2 +- Library/Homebrew/vendor/macho/macho.rb | 3 +- .../Homebrew/vendor/macho/macho/exceptions.rb | 89 ++- .../Homebrew/vendor/macho/macho/fat_file.rb | 169 ++++- .../Homebrew/vendor/macho/macho/headers.rb | 156 +++- .../vendor/macho/macho/load_commands.rb | 695 +++++++++++++----- .../Homebrew/vendor/macho/macho/macho_file.rb | 426 ++++++----- Library/Homebrew/vendor/macho/macho/open.rb | 10 +- .../Homebrew/vendor/macho/macho/sections.rb | 29 +- .../Homebrew/vendor/macho/macho/structure.rb | 24 +- Library/Homebrew/vendor/macho/macho/tools.rb | 49 +- Library/Homebrew/vendor/macho/macho/utils.rb | 126 +++- Library/Homebrew/vendor/macho/macho/view.rb | 23 + 13 files changed, 1296 insertions(+), 505 deletions(-) create mode 100644 Library/Homebrew/vendor/macho/macho/view.rb diff --git a/Library/Homebrew/vendor/README.md b/Library/Homebrew/vendor/README.md index c485718959..848ce703ff 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/Homebrew/ruby-macho), version 0.2.4 +* [ruby-macho](https://github.com/Homebrew/ruby-macho), version 0.2.5 ## Licenses: diff --git a/Library/Homebrew/vendor/macho/macho.rb b/Library/Homebrew/vendor/macho/macho.rb index c94eec77b7..5b0974b3f5 100644 --- a/Library/Homebrew/vendor/macho/macho.rb +++ b/Library/Homebrew/vendor/macho/macho.rb @@ -1,4 +1,5 @@ require "#{File.dirname(__FILE__)}/macho/structure" +require "#{File.dirname(__FILE__)}/macho/view" require "#{File.dirname(__FILE__)}/macho/headers" require "#{File.dirname(__FILE__)}/macho/load_commands" require "#{File.dirname(__FILE__)}/macho/sections" @@ -12,5 +13,5 @@ require "#{File.dirname(__FILE__)}/macho/tools" # The primary namespace for ruby-macho. module MachO # release version - VERSION = "0.2.4".freeze + VERSION = "0.2.5".freeze end diff --git a/Library/Homebrew/vendor/macho/macho/exceptions.rb b/Library/Homebrew/vendor/macho/macho/exceptions.rb index 6840060e88..262c195a35 100644 --- a/Library/Homebrew/vendor/macho/macho/exceptions.rb +++ b/Library/Homebrew/vendor/macho/macho/exceptions.rb @@ -3,6 +3,26 @@ module MachO class MachOError < RuntimeError end + # Raised when a Mach-O file modification fails. + class ModificationError < MachOError + end + + # Raised when a Mach-O file modification fails but can be recovered when + # operating on multiple Mach-O slices of a fat binary in non-strict mode. + class RecoverableModificationError < ModificationError + # @return [Fixnum, nil] The index of the Mach-O slice of a fat binary for + # which modification failed or `nil` if not a fat binary. This is used to + # make the error message more useful. + attr_accessor :macho_slice + + # @return [String] The exception message. + def to_s + s = super.to_s + s = "While modifying Mach-O slice #{@macho_slice}: #{s}" if @macho_slice + s + end + end + # Raised when a file is not a Mach-O. class NotAMachOError < MachOError # @param error [String] the error in question @@ -80,32 +100,89 @@ module MachO end end + # Raised when a load command can't be created manually. + class LoadCommandNotCreatableError < MachOError + # @param cmd_sym [Symbol] the uncreatable load command's symbol + def initialize(cmd_sym) + super "Load commands of type #{cmd_sym} cannot be created manually" + end + end + + # Raised when the number of arguments used to create a load command manually is wrong. + class LoadCommandCreationArityError < MachOError + # @param cmd_sym [Symbol] the load command's symbol + # @param expected_arity [Fixnum] the number of arguments expected + # @param actual_arity [Fixnum] the number of arguments received + def initialize(cmd_sym, expected_arity, actual_arity) + super "Expected #{expected_arity} arguments for #{cmd_sym} creation, got #{actual_arity}" + end + end + + # Raised when a load command can't be serialized. + class LoadCommandNotSerializableError < MachOError + # @param cmd_sym [Symbol] the load command's symbol + def initialize(cmd_sym) + super "Load commands of type #{cmd_sym} cannot be serialized" + end + end + + # Raised when a load command string is malformed in some way. + class LCStrMalformedError < MachOError + # @param lc [MachO::LoadCommand] the load command containing the string + def initialize(lc) + super "Load command #{lc.type} at offset #{lc.view.offset} contains a malformed string" + end + end + + # Raised when a change at an offset is not valid. + class OffsetInsertionError < ModificationError + # @param offset [Fixnum] the invalid offset + def initialize(offset) + super "Insertion at offset #{offset} is not valid" + end + end + # Raised when load commands are too large to fit in the current file. - class HeaderPadError < MachOError + class HeaderPadError < ModificationError # @param filename [String] the filename def initialize(filename) - super "Updated load commands do not fit in the header of " + - "#{filename}. #{filename} needs to be relinked, possibly with " + - "-headerpad or -headerpad_max_install_names" + super "Updated load commands do not fit in the header of " \ + "#{filename}. #{filename} needs to be relinked, possibly with " \ + "-headerpad or -headerpad_max_install_names" end end # Raised when attempting to change a dylib name that doesn't exist. - class DylibUnknownError < MachOError + class DylibUnknownError < RecoverableModificationError # @param dylib [String] the unknown shared library name def initialize(dylib) super "No such dylib name: #{dylib}" end end + # Raised when a dylib is missing an ID + class DylibIdMissingError < RecoverableModificationError + def initialize + super "Dylib is missing a dylib ID" + end + end + # Raised when attempting to change an rpath that doesn't exist. - class RpathUnknownError < MachOError + class RpathUnknownError < RecoverableModificationError # @param path [String] the unknown runtime path def initialize(path) super "No such runtime path: #{path}" end end + # Raised when attempting to add an rpath that already exists. + class RpathExistsError < RecoverableModificationError + # @param path [String] the extant path + def initialize(path) + super "#{path} already exists" + end + end + # Raised whenever unfinished code is called. class UnimplementedError < MachOError # @param thing [String] the thing that is unimplemented diff --git a/Library/Homebrew/vendor/macho/macho/fat_file.rb b/Library/Homebrew/vendor/macho/macho/fat_file.rb index dd24fa10fb..7924736489 100644 --- a/Library/Homebrew/vendor/macho/macho/fat_file.rb +++ b/Library/Homebrew/vendor/macho/macho/fat_file.rb @@ -30,22 +30,24 @@ module MachO # @param filename [String] the fat file to load from # @raise [ArgumentError] if the given file does not exist def initialize(filename) - raise ArgumentError.new("#{filename}: no such file") unless File.file?(filename) + raise ArgumentError, "#{filename}: no such file" unless File.file?(filename) @filename = filename - @raw_data = File.open(@filename, "rb") { |f| f.read } - @header = get_fat_header - @fat_archs = get_fat_archs - @machos = get_machos + @raw_data = File.open(@filename, "rb", &:read) + @header = populate_fat_header + @fat_archs = populate_fat_archs + @machos = populate_machos end + # Initializes a new FatFile instance from a binary string. + # @see MachO::FatFile.new_from_bin # @api private def initialize_from_bin(bin) @filename = nil @raw_data = bin - @header = get_fat_header - @fat_archs = get_fat_archs - @machos = get_machos + @header = populate_fat_header + @fat_archs = populate_fat_archs + @machos = populate_machos end # The file's raw fat data. @@ -115,7 +117,7 @@ module MachO end # The file's type. Assumed to be the same for every Mach-O within. - # @return [String] the filetype + # @return [Symbol] the filetype def filetype machos.first.filetype end @@ -124,34 +126,37 @@ module MachO # @example # file.dylib_id # => 'libBar.dylib' # @return [String, nil] the file's dylib ID + # @see MachO::MachOFile#linked_dylibs def dylib_id machos.first.dylib_id end # Changes the file's dylib ID to `new_id`. If the file is not a dylib, does nothing. # @example - # file.dylib_id = 'libFoo.dylib' + # file.change_dylib_id('libFoo.dylib') # @param new_id [String] the new dylib ID + # @param options [Hash] + # @option options [Boolean] :strict (true) if true, fail if one slice fails. + # if false, fail only if all slices fail. # @return [void] # @raise [ArgumentError] if `new_id` is not a String - def dylib_id=(new_id) - if !new_id.is_a?(String) - raise ArgumentError.new("argument must be a String") - end - - if !machos.all?(&:dylib?) - return nil - end + # @see MachO::MachOFile#linked_dylibs + def change_dylib_id(new_id, options = {}) + raise ArgumentError, "argument must be a String" unless new_id.is_a?(String) + return unless machos.all?(&:dylib?) - machos.each do |macho| - macho.dylib_id = new_id + each_macho(options) do |macho| + macho.change_dylib_id(new_id, options) end synchronize_raw_data end + alias dylib_id= change_dylib_id + # All shared libraries linked to the file's Mach-Os. # @return [Array<String>] an array of all shared libraries + # @see MachO::MachOFile#linked_dylibs def linked_dylibs # 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. @@ -165,16 +170,74 @@ module MachO # file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib') # @param old_name [String] the shared library name being changed # @param new_name [String] the new name - # @todo incomplete - def change_install_name(old_name, new_name) - machos.each do |macho| - macho.change_install_name(old_name, new_name) + # @param options [Hash] + # @option options [Boolean] :strict (true) if true, fail if one slice fails. + # if false, fail only if all slices fail. + # @return [void] + # @see MachO::MachOFile#change_install_name + def change_install_name(old_name, new_name, options = {}) + each_macho(options) do |macho| + macho.change_install_name(old_name, new_name, options) + end + + synchronize_raw_data + end + + alias change_dylib change_install_name + + # All runtime paths associated with the file's Mach-Os. + # @return [Array<String>] an array of all runtime paths + # @see MachO::MachOFile#rpaths + def rpaths + # Can individual architectures have different runtime paths? + machos.map(&:rpaths).flatten.uniq + end + + # Change the runtime path `old_path` to `new_path` in the file's Mach-Os. + # @param old_path [String] the old runtime path + # @param new_path [String] the new runtime path + # @param options [Hash] + # @option options [Boolean] :strict (true) if true, fail if one slice fails. + # if false, fail only if all slices fail. + # @return [void] + # @see MachO::MachOFile#change_rpath + def change_rpath(old_path, new_path, options = {}) + each_macho(options) do |macho| + macho.change_rpath(old_path, new_path, options) + end + + synchronize_raw_data + end + + # Add the given runtime path to the file's Mach-Os. + # @param path [String] the new runtime path + # @param options [Hash] + # @option options [Boolean] :strict (true) if true, fail if one slice fails. + # if false, fail only if all slices fail. + # @return [void] + # @see MachO::MachOFile#add_rpath + def add_rpath(path, options = {}) + each_macho(options) do |macho| + macho.add_rpath(path, options) end synchronize_raw_data end - alias :change_dylib :change_install_name + # Delete the given runtime path from the file's Mach-Os. + # @param path [String] the runtime path to delete + # @param options [Hash] + # @option options [Boolean] :strict (true) if true, fail if one slice fails. + # if false, fail only if all slices fail. + # @return void + # @see MachO::MachOFile#delete_rpath + def delete_rpath(path, options = {}) + each_macho(options) do |macho| + macho.delete_rpath(path, options) + end + + synchronize_raw_data + end # Extract a Mach-O with the given CPU type from the file. # @example @@ -197,7 +260,7 @@ module MachO # @note Overwrites all data in the file! def write! if filename.nil? - raise MachOError.new("cannot write to a default file when initialized from a binary string") + raise MachOError, "cannot write to a default file when initialized from a binary string" else File.open(@filename, "wb") { |f| f.write(@raw_data) } end @@ -211,15 +274,15 @@ module MachO # @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 + # @api private + def populate_fat_header # the smallest fat Mach-O header is 8 bytes - raise TruncatedFileError.new if @raw_data.size < 8 + raise TruncatedFileError if @raw_data.size < 8 fh = FatHeader.new_from_bin(:big, @raw_data[0, FatHeader.bytesize]) - raise MagicError.new(fh.magic) unless MachO.magic?(fh.magic) - raise MachOBinaryError.new unless MachO.fat_magic?(fh.magic) + raise MagicError, fh.magic unless Utils.magic?(fh.magic) + raise MachOBinaryError unless Utils.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 @@ -228,15 +291,15 @@ module MachO # 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 + raise JavaClassFileError if fh.nfat_arch > 30 fh end # Obtain an array of fat architectures from raw file data. # @return [Array<MachO::FatArch>] an array of fat architectures - # @private - def get_fat_archs + # @api private + def populate_fat_archs archs = [] fa_off = FatHeader.bytesize @@ -250,8 +313,8 @@ module MachO # Obtain an array of Mach-O blobs from raw file data. # @return [Array<MachO::MachOFile>] an array of Mach-Os - # @private - def get_machos + # @api private + def populate_machos machos = [] fat_archs.each do |arch| @@ -261,9 +324,37 @@ module MachO machos end - # @todo this needs to be redesigned. arch[:offset] and arch[:size] are - # already out-of-date, and the header needs to be synchronized as well. - # @private + # Yield each Mach-O object in the file, rescuing and accumulating errors. + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if at least one Mach-O raises an exception. If false, + # only raises an exception if *all* Mach-Os raise exceptions. + # @raise [MachO::RecoverableModificationError] under the conditions of + # the `:strict` option above. + # @api private + def each_macho(options = {}) + strict = options.fetch(:strict, true) + errors = [] + + machos.each_with_index do |macho, index| + begin + yield macho + rescue RecoverableModificationError => error + error.macho_slice = index + + # Strict mode: Immediately re-raise. Otherwise: Retain, check later. + raise error if strict + errors << error + end + end + + # Non-strict mode: Raise first error if *all* Mach-O slices failed. + raise errors.first if errors.size == machos.size + end + + # Synchronize the raw file data with each internal Mach-O object. + # @return [void] + # @api private def synchronize_raw_data machos.each_with_index do |macho, i| arch = fat_archs[i] diff --git a/Library/Homebrew/vendor/macho/macho/headers.rb b/Library/Homebrew/vendor/macho/macho/headers.rb index 79b5a1802f..7272503af9 100644 --- a/Library/Homebrew/vendor/macho/macho/headers.rb +++ b/Library/Homebrew/vendor/macho/macho/headers.rb @@ -1,64 +1,82 @@ module MachO # big-endian fat magic + # @api private FAT_MAGIC = 0xcafebabe # little-endian fat magic # this is defined, but should never appear in ruby-macho code because # fat headers are always big-endian and therefore always unpacked as such. + # @api private FAT_CIGAM = 0xbebafeca # 32-bit big-endian magic + # @api private MH_MAGIC = 0xfeedface # 32-bit little-endian magic + # @api private MH_CIGAM = 0xcefaedfe # 64-bit big-endian magic + # @api private MH_MAGIC_64 = 0xfeedfacf # 64-bit little-endian magic + # @api private MH_CIGAM_64 = 0xcffaedfe # association of magic numbers to string representations + # @api private MH_MAGICS = { FAT_MAGIC => "FAT_MAGIC", MH_MAGIC => "MH_MAGIC", MH_CIGAM => "MH_CIGAM", MH_MAGIC_64 => "MH_MAGIC_64", - MH_CIGAM_64 => "MH_CIGAM_64" - } + MH_CIGAM_64 => "MH_CIGAM_64", + }.freeze # mask for CPUs with 64-bit architectures (when running a 64-bit ABI?) + # @api private CPU_ARCH_ABI64 = 0x01000000 # any CPU (unused?) + # @api private CPU_TYPE_ANY = -1 # m68k compatible CPUs + # @api private CPU_TYPE_MC680X0 = 0x06 # i386 and later compatible CPUs + # @api private CPU_TYPE_I386 = 0x07 # x86_64 (AMD64) compatible CPUs + # @api private CPU_TYPE_X86_64 = (CPU_TYPE_I386 | CPU_ARCH_ABI64) # 32-bit ARM compatible CPUs + # @api private CPU_TYPE_ARM = 0x0c # m88k compatible CPUs + # @api private CPU_TYPE_MC88000 = 0xd # 64-bit ARM compatible CPUs + # @api private CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64) # PowerPC compatible CPUs + # @api private CPU_TYPE_POWERPC = 0x12 # PowerPC64 compatible CPUs + # @api private CPU_TYPE_POWERPC64 = (CPU_TYPE_POWERPC | CPU_ARCH_ABI64) # association of cpu types to symbol representations + # @api private CPU_TYPES = { CPU_TYPE_ANY => :any, CPU_TYPE_I386 => :i386, @@ -67,156 +85,213 @@ module MachO CPU_TYPE_ARM64 => :arm64, CPU_TYPE_POWERPC => :ppc, CPU_TYPE_POWERPC64 => :ppc64, - } + }.freeze # mask for CPU subtype capabilities + # @api private CPU_SUBTYPE_MASK = 0xff000000 # 64-bit libraries (undocumented!) # @see http://llvm.org/docs/doxygen/html/Support_2MachO_8h_source.html + # @api private CPU_SUBTYPE_LIB64 = 0x80000000 # the lowest common sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_I386 = 3 # the i486 sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_486 = 4 # the i486SX sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_486SX = 132 # the i586 (P5, Pentium) sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_586 = 5 - CPU_SUBTYPE_PENT = CPU_SUBTYPE_586 + + # @see CPU_SUBTYPE_586 + # @api private + CPU_SUBTYPE_PENT = CPU_SUBTYPE_586 # the Pentium Pro (P6) sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_PENTPRO = 22 # the Pentium II (P6, M3?) sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_PENTII_M3 = 54 # the Pentium II (P6, M5?) sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_PENTII_M5 = 86 # the Pentium 4 (Netburst) sub-type for `CPU_TYPE_I386` + # @api private CPU_SUBTYPE_PENTIUM_4 = 10 # the lowest common sub-type for `CPU_TYPE_MC680X0` + # @api private CPU_SUBTYPE_MC680X0_ALL = 1 + + # @see CPU_SUBTYPE_MC680X0_ALL + # @api private CPU_SUBTYPE_MC68030 = CPU_SUBTYPE_MC680X0_ALL # the 040 subtype for `CPU_TYPE_MC680X0` + # @api private CPU_SUBTYPE_MC68040 = 2 # the 030 subtype for `CPU_TYPE_MC680X0` + # @api private CPU_SUBTYPE_MC68030_ONLY = 3 # the lowest common sub-type for `CPU_TYPE_X86_64` + # @api private CPU_SUBTYPE_X86_64_ALL = CPU_SUBTYPE_I386 # the Haskell sub-type for `CPU_TYPE_X86_64` + # @api private CPU_SUBTYPE_X86_64_H = 8 # the lowest common sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_ALL = 0 # the v4t sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V4T = 5 # the v6 sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V6 = 6 # the v5 sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V5TEJ = 7 # the xscale (v5 family) sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_XSCALE = 8 # the v7 sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7 = 9 # the v7f (Cortex A9) sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7F = 10 # the v7s ("Swift") sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7S = 11 # the v7k ("Kirkwood40") sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7K = 12 # the v6m sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V6M = 14 # the v7m sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7M = 15 # the v7em sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V7EM = 16 # the v8 sub-type for `CPU_TYPE_ARM` + # @api private CPU_SUBTYPE_ARM_V8 = 13 # the lowest common sub-type for `CPU_TYPE_ARM64` + # @api private CPU_SUBTYPE_ARM64_ALL = 0 # the v8 sub-type for `CPU_TYPE_ARM64` + # @api private CPU_SUBTYPE_ARM64_V8 = 1 # the lowest common sub-type for `CPU_TYPE_MC88000` + # @api private CPU_SUBTYPE_MC88000_ALL = 0 + + # @see CPU_SUBTYPE_MC88000_ALL + # @api private CPU_SUBTYPE_MMAX_JPC = CPU_SUBTYPE_MC88000_ALL # the 100 sub-type for `CPU_TYPE_MC88000` + # @api private CPU_SUBTYPE_MC88100 = 1 # the 110 sub-type for `CPU_TYPE_MC88000` + # @api private CPU_SUBTYPE_MC88110 = 2 # the lowest common sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_ALL = 0 # the 601 sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_601 = 1 # the 602 sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_602 = 2 # the 603 sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_603 = 3 # the 603e (G2) sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_603E = 4 # the 603ev sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_603EV = 5 # the 604 sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_604 = 6 # the 604e sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_604E = 7 # the 620 sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_620 = 8 # the 750 (G3) sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_750 = 9 # the 7400 (G4) sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_7400 = 10 # the 7450 (G4 "Voyager") sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_7450 = 11 # the 970 (G5) sub-type for `CPU_TYPE_POWERPC` + # @api private CPU_SUBTYPE_POWERPC_970 = 100 # any CPU sub-type for CPU type `CPU_TYPE_POWERPC64` + # @api private CPU_SUBTYPE_POWERPC64_ALL = CPU_SUBTYPE_POWERPC_ALL # association of CPU types/subtype pairs to symbol representations in # (very) roughly descending order of commonness # @see https://opensource.apple.com/source/cctools/cctools-877.8/libstuff/arch.c + # @api private CPU_SUBTYPES = { CPU_TYPE_I386 => { CPU_SUBTYPE_I386 => :i386, @@ -280,53 +355,64 @@ module MachO }.freeze # relocatable object file + # @api private MH_OBJECT = 0x1 # demand paged executable file + # @api private MH_EXECUTE = 0x2 # fixed VM shared library file + # @api private MH_FVMLIB = 0x3 # core dump file + # @api private MH_CORE = 0x4 # preloaded executable file + # @api private MH_PRELOAD = 0x5 # dynamically bound shared library + # @api private MH_DYLIB = 0x6 # dynamic link editor + # @api private MH_DYLINKER = 0x7 # dynamically bound bundle file + # @api private MH_BUNDLE = 0x8 # shared library stub for static linking only, no section contents + # @api private MH_DYLIB_STUB = 0x9 # companion file with only debug sections + # @api private MH_DSYM = 0xa # x86_64 kexts + # @api private MH_KEXT_BUNDLE = 0xb - # association of filetypes to string representations + # association of filetypes to Symbol representations # @api private MH_FILETYPES = { - MH_OBJECT => "MH_OBJECT", - MH_EXECUTE => "MH_EXECUTE", - MH_FVMLIB => "MH_FVMLIB", - MH_CORE => "MH_CORE", - MH_PRELOAD => "MH_PRELOAD", - MH_DYLIB => "MH_DYLIB", - MH_DYLINKER => "MH_DYLINKER", - MH_BUNDLE => "MH_BUNDLE", - MH_DYLIB_STUB => "MH_DYLIB_STUB", - MH_DSYM => "MH_DSYM", - MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE" - } + MH_OBJECT => :object, + MH_EXECUTE => :execute, + MH_FVMLIB => :fvmlib, + MH_CORE => :core, + MH_PRELOAD => :preload, + MH_DYLIB => :dylib, + MH_DYLINKER => :dylinker, + MH_BUNDLE => :bundle, + MH_DYLIB_STUB => :dylib_stub, + MH_DSYM => :dsym, + MH_KEXT_BUNDLE => :kext_bundle, + }.freeze # association of mach header flag symbols to values # @api private @@ -356,8 +442,8 @@ module MachO :MH_DEAD_STRIPPABLE_DYLIB => 0x400000, :MH_HAS_TLV_DESCRIPTORS => 0x800000, :MH_NO_HEAP_EXECUTION => 0x1000000, - :MH_APP_EXTENSION_SAFE => 0x02000000 - } + :MH_APP_EXTENSION_SAFE => 0x02000000, + }.freeze # Fat binary header structure # @see MachO::FatArch @@ -369,7 +455,12 @@ module MachO attr_reader :nfat_arch # always big-endian - FORMAT = "N2" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "N2".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 8 # @api private @@ -399,7 +490,12 @@ module MachO attr_reader :align # always big-endian - FORMAT = "N5" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "N5".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 20 # @api private @@ -435,12 +531,17 @@ module MachO # @return [Fixnum] the header flags associated with the Mach-O attr_reader :flags - FORMAT = "L=7" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=7".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 28 # @api private def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, - flags) + flags) @magic = magic @cputype = cputype # For now we're not interested in additional capability bits also to be @@ -468,12 +569,17 @@ module MachO # @return [void] attr_reader :reserved - FORMAT = "L=8" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=8".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 32 # @api private def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, - flags, reserved) + flags, reserved) super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags) @reserved = reserved end diff --git a/Library/Homebrew/vendor/macho/macho/load_commands.rb b/Library/Homebrew/vendor/macho/macho/load_commands.rb index 0e7695ee9a..3eb130bb82 100644 --- a/Library/Homebrew/vendor/macho/macho/load_commands.rb +++ b/Library/Homebrew/vendor/macho/macho/load_commands.rb @@ -1,6 +1,7 @@ module MachO # load commands added after OS X 10.1 need to be bitwise ORed with # LC_REQ_DYLD to be recognized by the dynamic linder (dyld) + # @api private LC_REQ_DYLD = 0x80000000 # association of load commands to symbol representations @@ -57,6 +58,10 @@ module MachO 0x30 => :LC_VERSION_MIN_WATCHOS, }.freeze + # association of symbol representations to load command constants + # @api private + LOAD_COMMAND_CONSTANTS = LOAD_COMMANDS.invert.freeze + # load commands responsible for loading dylibs # @api private DYLIB_LOAD_COMMANDS = [ @@ -67,19 +72,27 @@ module MachO :LC_LOAD_UPWARD_DYLIB, ].freeze + # load commands that can be created manually via {LoadCommand.create} + # @api private + CREATABLE_LOAD_COMMANDS = DYLIB_LOAD_COMMANDS + [ + :LC_ID_DYLIB, + :LC_RPATH, + :LC_LOAD_DYLINKER, + ].freeze + # association of load command symbols to string representations of classes # @api private LC_STRUCTURES = { :LC_SEGMENT => "SegmentCommand", :LC_SYMTAB => "SymtabCommand", - :LC_SYMSEG => "LoadCommand", # obsolete + :LC_SYMSEG => "SymsegCommand", # obsolete :LC_THREAD => "ThreadCommand", # seems obsolete, but not documented as such :LC_UNIXTHREAD => "ThreadCommand", - :LC_LOADFVMLIB => "LoadCommand", # obsolete - :LC_IDFVMLIB => "LoadCommand", # obsolete - :LC_IDENT => "LoadCommand", # obsolete - :LC_FVMFILE => "LoadCommand", # reserved for internal use only - :LC_PREPAGE => "LoadCommand", # reserved for internal use only + :LC_LOADFVMLIB => "FvmlibCommand", # obsolete + :LC_IDFVMLIB => "FvmlibCommand", # obsolete + :LC_IDENT => "IdentCommand", # obsolete + :LC_FVMFILE => "FvmfileCommand", # reserved for internal use only + :LC_PREPAGE => "LoadCommand", # reserved for internal use only, no public struct :LC_DYSYMTAB => "DysymtabCommand", :LC_LOAD_DYLIB => "DylibCommand", :LC_ID_DYLIB => "DylibCommand", @@ -148,8 +161,8 @@ module MachO # represented, and no actual data. Used when a more specific class # isn't available/implemented. class LoadCommand < MachOStructure - # @return [Fixnum] the offset in the file the command was created from - attr_reader :offset + # @return [MachO::MachOView] the raw view associated with the load command + attr_reader :view # @return [Fixnum] the load command's identifying number attr_reader :cmd @@ -157,40 +170,80 @@ module MachO # @return [Fixnum] the size of the load command, in bytes attr_reader :cmdsize - FORMAT = "L=2" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 8 - # Creates a new LoadCommand given an offset and binary string - # @param raw_data [String] the raw Mach-O data - # @param endianness [Symbol] the endianness of the command (:big or :little) - # @param offset [Fixnum] the offset to initialize with - # @param bin [String] the binary string to initialize with + # Instantiates a new LoadCommand given a view into its origin Mach-O + # @param view [MachO::MachOView] the load command's raw view # @return [MachO::LoadCommand] the new load command # @api private - def self.new_from_bin(raw_data, endianness, offset, bin) - format = specialize_format(self::FORMAT, endianness) + def self.new_from_bin(view) + bin = view.raw_data.slice(view.offset, bytesize) + format = Utils.specialize_format(self::FORMAT, view.endianness) + + new(view, *bin.unpack(format)) + end + + # Creates a new (viewless) command corresponding to the symbol provided + # @param cmd_sym [Symbol] the symbol of the load command being created + # @param args [Array] the arguments for the load command being created + def self.create(cmd_sym, *args) + raise LoadCommandNotCreatableError, cmd_sym unless CREATABLE_LOAD_COMMANDS.include?(cmd_sym) + + klass = MachO.const_get LC_STRUCTURES[cmd_sym] + cmd = LOAD_COMMAND_CONSTANTS[cmd_sym] - self.new(raw_data, offset, *bin.unpack(format)) + # cmd will be filled in, view and cmdsize will be left unpopulated + klass_arity = klass.instance_method(:initialize).arity - 3 + + raise LoadCommandCreationArityError.new(cmd_sym, klass_arity, args.size) if klass_arity != args.size + + klass.new(nil, cmd, nil, *args) end - # @param raw_data [String] the raw Mach-O data - # @param offset [Fixnum] the offset to initialize with + # @param view [MachO::MachOView] the load command's raw view # @param cmd [Fixnum] the load command's identifying number # @param cmdsize [Fixnum] the size of the load command in bytes # @api private - def initialize(raw_data, offset, cmd, cmdsize) - @raw_data = raw_data - @offset = offset + def initialize(view, cmd, cmdsize) + @view = view @cmd = cmd @cmdsize = cmdsize end + # @return [Boolean] true if the load command can be serialized, false otherwise + def serializable? + CREATABLE_LOAD_COMMANDS.include?(LOAD_COMMANDS[cmd]) + end + + # @param context [MachO::LoadCommand::SerializationContext] the context + # to serialize into + # @return [String, nil] the serialized fields of the load command, or nil + # if the load command can't be serialized + # @api private + def serialize(context) + raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable? + format = Utils.specialize_format(FORMAT, context.endianness) + [cmd, SIZEOF].pack(format) + end + + # @return [Fixnum] the load command's offset in the source file + # @deprecated use {#view} instead + def offset + view.offset + end + # @return [Symbol] a symbol representation of the load command's identifying number def type LOAD_COMMANDS[cmd] end - alias :to_sym :type + alias to_sym type # @return [String] a string representation of the load command's identifying number def to_s @@ -202,25 +255,62 @@ module MachO # pretend that strings stored in LCs are immediately available without # explicit operations on the raw Mach-O data. class LCStr - # @param raw_data [String] the raw Mach-O data. # @param lc [MachO::LoadCommand] the load command - # @param lc_str [Fixnum] the offset to the beginning of the string + # @param lc_str [Fixnum, String] the offset to the beginning of the string, + # or the string itself if not being initialized with a view. + # @raise [MachO::LCStrMalformedError] if the string is malformed + # @todo devise a solution such that the `lc_str` parameter is not + # interpreted differently depending on `lc.view`. The current behavior + # is a hack to allow viewless load command creation. # @api private - def initialize(raw_data, lc, lc_str) - @raw_data = raw_data - @lc = lc - @lc_str = lc_str - @str = @raw_data.slice(@lc.offset + @lc_str...@lc.offset + @lc.cmdsize).delete("\x00") + def initialize(lc, lc_str) + view = lc.view + + if view + lc_str_abs = view.offset + lc_str + lc_end = view.offset + lc.cmdsize - 1 + raw_string = view.raw_data.slice(lc_str_abs..lc_end) + @string, null_byte, _padding = raw_string.partition("\x00") + raise LCStrMalformedError, lc if null_byte.empty? + @string_offset = lc_str + else + @string = lc_str + @string_offset = 0 + end end # @return [String] a string representation of the LCStr def to_s - @str + @string end # @return [Fixnum] the offset to the beginning of the string in the load command def to_i - @lc_str + @string_offset + end + end + + # Represents the contextual information needed by a load command to + # serialize itself correctly into a binary string. + class SerializationContext + # @return [Symbol] the endianness of the serialized load command + attr_reader :endianness + + # @return [Fixnum] the constant alignment value used to pad the serialized load command + attr_reader :alignment + + # @param macho [MachO::MachOFile] the file to contextualize + # @return [MachO::LoadCommand::SerializationContext] the resulting context + def self.context_for(macho) + new(macho.endianness, macho.alignment) + end + + # @param endianness [Symbol] the endianness of the context + # @param alignment [Fixnum] the alignment of the context + # @api private + def initialize(endianness, alignment) + @endianness = endianness + @alignment = alignment end end end @@ -231,12 +321,17 @@ module MachO # @return [Array<Fixnum>] the UUID attr_reader :uuid - FORMAT = "L=2a16" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2a16".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, uuid) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, uuid) + super(view, cmd, cmdsize) @uuid = uuid.unpack("C16") # re-unpack for the actual UUID array end @@ -282,13 +377,18 @@ module MachO # @return [Fixnum] any flags associated with the segment attr_reader :flags - FORMAT = "L=2a16L=4l=2L=2" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2a16L=4l=2L=2".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 56 # @api private - def initialize(raw_data, offset, cmd, cmdsize, segname, vmaddr, vmsize, fileoff, - filesize, maxprot, initprot, nsects, flags) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff, + filesize, maxprot, initprot, nsects, flags) + super(view, cmd, cmdsize) @segname = segname.delete("\x00") @vmaddr = vmaddr @vmsize = vmsize @@ -300,6 +400,23 @@ module MachO @flags = flags end + # All sections referenced within this segment. + # @return [Array<MachO::Section>] if the Mach-O is 32-bit + # @return [Array<MachO::Section64>] if the Mach-O is 64-bit + def sections + klass = case self + when MachO::SegmentCommand64 + MachO::Section64 + when MachO::SegmentCommand + MachO::Section + end + + bins = view.raw_data[view.offset + self.class.bytesize, nsects * klass.bytesize] + bins.unpack("a#{klass.bytesize}" * nsects).map do |bin| + klass.new_from_bin(view.endianness, bin) + end + end + # @example # puts "this segment relocated in/to it" if sect.flag?(:SG_NORELOC) # @param flag [Symbol] a segment flag symbol @@ -313,61 +430,14 @@ module MachO # A load command indicating that part of this file is to be mapped into # the task's address space. Corresponds to LC_SEGMENT_64. - class SegmentCommand64 < LoadCommand - # @return [String] the name of the segment - attr_reader :segname - - # @return [Fixnum] the memory address of the segment - attr_reader :vmaddr - - # @return [Fixnum] the memory size of the segment - attr_reader :vmsize - - # @return [Fixnum] the file offset of the segment - attr_reader :fileoff - - # @return [Fixnum] the amount to map from the file - attr_reader :filesize - - # @return [Fixnum] the maximum VM protection - attr_reader :maxprot - - # @return [Fixnum] the initial VM protection - attr_reader :initprot - - # @return [Fixnum] the number of sections in the segment - attr_reader :nsects - - # @return [Fixnum] any flags associated with the segment - attr_reader :flags - - FORMAT = "L=2a16Q=4l=2L=2" - SIZEOF = 72 - + class SegmentCommand64 < SegmentCommand + # @see MachOStructure::FORMAT # @api private - def initialize(raw_data, offset, cmd, cmdsize, segname, vmaddr, vmsize, fileoff, - filesize, maxprot, initprot, nsects, flags) - super(raw_data, offset, cmd, cmdsize) - @segname = segname.delete("\x00") - @vmaddr = vmaddr - @vmsize = vmsize - @fileoff = fileoff - @filesize = filesize - @maxprot = maxprot - @initprot = initprot - @nsects = nsects - @flags = flags - end + FORMAT = "L=2a16Q=4l=2L=2".freeze - # @example - # puts "this segment relocated in/to it" if sect.flag?(:SG_NORELOC) - # @param flag [Symbol] a segment flag symbol - # @return [Boolean] true if `flag` is present in the segment's flag field - def flag?(flag) - flag = SEGMENT_FLAGS[flag] - return false if flag.nil? - flags & flag == flag - end + # @see MachOStructure::SIZEOF + # @api private + SIZEOF = 72 end # A load command representing some aspect of shared libraries, depending @@ -386,18 +456,33 @@ module MachO # @return [Fixnum] the library's compatibility version number attr_reader :compatibility_version - FORMAT = "L=6" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=6".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, name, timestamp, current_version, - compatibility_version) - super(raw_data, offset, cmd, cmdsize) - @name = LCStr.new(raw_data, self, name) + def initialize(view, cmd, cmdsize, name, timestamp, current_version, compatibility_version) + super(view, cmd, cmdsize) + @name = LCStr.new(self, name) @timestamp = timestamp @current_version = current_version @compatibility_version = compatibility_version end + + # @param context [MachO::LoadCcommand::SerializationContext] the context + # @return [String] the serialized fields of the load command + # @api private + def serialize(context) + format = Utils.specialize_format(FORMAT, context.endianness) + string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s) + cmdsize = SIZEOF + string_payload.bytesize + [cmd, cmdsize, string_offsets[:name], timestamp, current_version, + compatibility_version].pack(format) + string_payload + end end # A load command representing some aspect of the dynamic linker, depending @@ -407,13 +492,28 @@ module MachO # @return [MachO::LoadCommand::LCStr] the dynamic linker's path name as an LCStr attr_reader :name - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, name) - super(raw_data, offset, cmd, cmdsize) - @name = LCStr.new(raw_data, self, name) + def initialize(view, cmd, cmdsize, name) + super(view, cmd, cmdsize) + @name = LCStr.new(self, name) + end + + # @param context [MachO::LoadCcommand::SerializationContext] the context + # @return [String] the serialized fields of the load command + # @api private + def serialize(context) + format = Utils.specialize_format(FORMAT, context.endianness) + string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s) + cmdsize = SIZEOF + string_payload.bytesize + [cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload end end @@ -429,13 +529,18 @@ module MachO # @return [Fixnum] a bit vector of linked modules attr_reader :linked_modules - FORMAT = "L=5" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=5".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 20 # @api private - def initialize(raw_data, offset, cmd, cmdsize, name, nmodules, linked_modules) - super(raw_data, offset, cmd, cmdsize) - @name = LCStr.new(raw_data, self, name) + def initialize(view, cmd, cmdsize, name, nmodules, linked_modules) + super(view, cmd, cmdsize) + @name = LCStr.new(self, name) @nmodules = nmodules @linked_modules = linked_modules end @@ -444,7 +549,12 @@ module MachO # A load command used to represent threads. # @note cctools-870 has all fields of thread_command commented out except common ones (cmd, cmdsize) class ThreadCommand < LoadCommand - FORMAT = "L=2" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 8 end @@ -476,14 +586,18 @@ module MachO # @return [void] attr_reader :reserved6 - FORMAT = "L=10" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=10".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 40 # @api private - def initialize(raw_data, offset, cmd, cmdsize, init_address, init_module, - reserved1, reserved2, reserved3, reserved4, reserved5, - reserved6) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, init_address, init_module, reserved1, + reserved2, reserved3, reserved4, reserved5, reserved6) + super(view, cmd, cmdsize) @init_address = init_address @init_module = init_module @reserved1 = reserved1 @@ -523,14 +637,18 @@ module MachO # @return [void] attr_reader :reserved6 - FORMAT = "L=2Q=8" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2Q=8".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 72 # @api private - def initialize(raw_data, offset, cmd, cmdsize, init_address, init_module, - reserved1, reserved2, reserved3, reserved4, reserved5, - reserved6) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, init_address, init_module, reserved1, + reserved2, reserved3, reserved4, reserved5, reserved6) + super(view, cmd, cmdsize) @init_address = init_address @init_module = init_module @reserved1 = reserved1 @@ -548,13 +666,18 @@ module MachO # @return [MachO::LoadCommand::LCStr] the umbrella framework name as an LCStr attr_reader :umbrella - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, umbrella) - super(raw_data, offset, cmd, cmdsize) - @umbrella = LCStr.new(raw_data, self, umbrella) + def initialize(view, cmd, cmdsize, umbrella) + super(view, cmd, cmdsize) + @umbrella = LCStr.new(self, umbrella) end end @@ -564,13 +687,18 @@ module MachO # @return [MachO::LoadCommand::LCStr] the subumbrella framework name as an LCStr attr_reader :sub_umbrella - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, sub_umbrella) - super(raw_data, offset, cmd, cmdsize) - @sub_umbrella = LCStr.new(raw_data, self, sub_umbrella) + def initialize(view, cmd, cmdsize, sub_umbrella) + super(view, cmd, cmdsize) + @sub_umbrella = LCStr.new(self, sub_umbrella) end end @@ -580,13 +708,18 @@ module MachO # @return [MachO::LoadCommand::LCStr] the sublibrary name as an LCStr attr_reader :sub_library - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, sub_library) - super(raw_data, offset, cmd, cmdsize) - @sub_library = LCStr.new(raw_data, self, sub_library) + def initialize(view, cmd, cmdsize, sub_library) + super(view, cmd, cmdsize) + @sub_library = LCStr.new(self, sub_library) end end @@ -596,13 +729,18 @@ module MachO # @return [MachO::LoadCommand::LCStr] the subclient name as an LCStr attr_reader :sub_client - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, sub_client) - super(raw_data, offset, cmd, cmdsize) - @sub_client = LCStr.new(raw_data, self, sub_client) + def initialize(view, cmd, cmdsize, sub_client) + super(view, cmd, cmdsize) + @sub_client = LCStr.new(self, sub_client) end end @@ -621,12 +759,17 @@ module MachO # @return the string table size in bytes attr_reader :strsize - FORMAT = "L=6" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=6".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, symoff, nsyms, stroff, strsize) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, symoff, nsyms, stroff, strsize) + super(view, cmd, cmdsize) @symoff = symoff @nsyms = nsyms @stroff = stroff @@ -691,17 +834,21 @@ module MachO # @return [Fixnum] the number of local relocation entries attr_reader :nlocrel + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=20".freeze - FORMAT = "L=20" + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 80 # ugh # @api private - def initialize(raw_data, offset, cmd, cmdsize, ilocalsym, nlocalsym, iextdefsym, - nextdefsym, iundefsym, nundefsym, tocoff, ntoc, modtaboff, - nmodtab, extrefsymoff, nextrefsyms, indirectsymoff, - nindirectsyms, extreloff, nextrel, locreloff, nlocrel) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, ilocalsym, nlocalsym, iextdefsym, + nextdefsym, iundefsym, nundefsym, tocoff, ntoc, modtaboff, + nmodtab, extrefsymoff, nextrefsyms, indirectsymoff, + nindirectsyms, extreloff, nextrel, locreloff, nlocrel) + super(view, cmd, cmdsize) @ilocalsym = ilocalsym @nlocalsym = nlocalsym @iextdefsym = iextdefsym @@ -732,14 +879,58 @@ module MachO # @return [Fixnum] the number of hints in the hint table attr_reader :nhints - FORMAT = "L=4" + # @return [MachO::TwolevelHintsCommand::TwolevelHintTable] the hint table + attr_reader :table + + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=4".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 16 # @api private - def initialize(raw_data, offset, cmd, cmdsize, htoffset, nhints) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, htoffset, nhints) + super(view, cmd, cmdsize) @htoffset = htoffset @nhints = nhints + @table = TwolevelHintsTable.new(view, htoffset, nhints) + end + + # A representation of the two-level namespace lookup hints table exposed + # by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`). + class TwolevelHintsTable + # @return [Array<MachO::TwoLevelHintsTable::TwoLevelHint>] all hints in the table + attr_reader :hints + + # @param view [MachO::MachOView] the view into the current Mach-O + # @param htoffset [Fixnum] the offset of the hints table + # @param nhints [Fixnum] the number of two-level hints in the table + # @api private + def initialize(view, htoffset, nhints) + format = Utils.specialize_format("L=#{nhints}", view.endianness) + raw_table = view.raw_data[htoffset, nhints * 4] + blobs = raw_table.unpack(format) + + @hints = blobs.map { |b| TwolevelHint.new(b) } + end + + # An individual two-level namespace lookup hint. + class TwolevelHint + # @return [Fixnum] the index into the sub-images + attr_reader :isub_image + + # @return [Fixnum] the index into the table of contents + attr_reader :itoc + + # @param blob [Fixnum] the 32-bit number containing the lookup hint + # @api private + def initialize(blob) + @isub_image = blob >> 24 + @itoc = blob & 0x00FFFFFF + end + end end end @@ -749,12 +940,17 @@ module MachO # @return [Fixnum] the checksum or 0 attr_reader :cksum - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, cksum) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, cksum) + super(view, cmd, cmdsize) @cksum = cksum end end @@ -766,13 +962,28 @@ module MachO # @return [MachO::LoadCommand::LCStr] the path to add to the run path as an LCStr attr_reader :path - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, path) - super(raw_data, offset, cmd, cmdsize) - @path = LCStr.new(raw_data, self, path) + def initialize(view, cmd, cmdsize, path) + super(view, cmd, cmdsize) + @path = LCStr.new(self, path) + end + + # @param context [MachO::LoadCcommand::SerializationContext] the context + # @return [String] the serialized fields of the load command + # @api private + def serialize(context) + format = Utils.specialize_format(FORMAT, context.endianness) + string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :path => path.to_s) + cmdsize = SIZEOF + string_payload.bytesize + [cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload end end @@ -786,12 +997,17 @@ module MachO # @return [Fixnum] size of the data in the __LINKEDIT segment attr_reader :datasize - FORMAT = "L=4" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=4".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 16 # @api private - def initialize(raw_data, offset, cmd, cmdsize, dataoff, datasize) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, dataoff, datasize) + super(view, cmd, cmdsize) @dataoff = dataoff @datasize = datasize end @@ -809,12 +1025,17 @@ module MachO # @return [Fixnum] the encryption system, or 0 if not encrypted yet attr_reader :cryptid - FORMAT = "L=5" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=5".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 20 # @api private - def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid) + super(view, cmd, cmdsize) @cryptoff = cryptoff @cryptsize = cryptsize @cryptid = cryptid @@ -836,12 +1057,17 @@ module MachO # @return [Fixnum] 64-bit padding value attr_reader :pad - FORMAT = "L=6" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=6".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad) + super(view, cmd, cmdsize) @cryptoff = cryptoff @cryptsize = cryptsize @cryptid = cryptid @@ -858,12 +1084,17 @@ module MachO # @return [Fixnum] the SDK version X.Y.Z packed as x16.y8.z8 attr_reader :sdk - FORMAT = "L=4" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=4".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 16 # @api private - def initialize(raw_data, offset, cmd, cmdsize, version, sdk) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, version, sdk) + super(view, cmd, cmdsize) @version = version @sdk = sdk end @@ -925,14 +1156,19 @@ module MachO # @return [Fixnum] the size of the export information attr_reader :export_size - FORMAT = "L=12" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=12".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 48 # @api private - def initialize(raw_data, offset, cmd, cmdsize, rebase_off, rebase_size, bind_off, - bind_size, weak_bind_off, weak_bind_size, lazy_bind_off, - lazy_bind_size, export_off, export_size) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, rebase_off, rebase_size, bind_off, + bind_size, weak_bind_off, weak_bind_size, lazy_bind_off, + lazy_bind_size, export_off, export_size) + super(view, cmd, cmdsize) @rebase_off = rebase_off @rebase_size = rebase_size @bind_off = bind_off @@ -952,12 +1188,17 @@ module MachO # @return [Fixnum] the number of strings attr_reader :count - FORMAT = "L=3" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=3".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 12 # @api private - def initialize(raw_data, offset, cmd, cmdsize, count) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, count) + super(view, cmd, cmdsize) @count = count end end @@ -970,12 +1211,17 @@ module MachO # @return [Fixnum] if not 0, the initial stack size. attr_reader :stacksize - FORMAT = "L=2Q=2" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2Q=2".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 24 # @api private - def initialize(raw_data, offset, cmd, cmdsize, entryoff, stacksize) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, entryoff, stacksize) + super(view, cmd, cmdsize) @entryoff = entryoff @stacksize = stacksize end @@ -987,12 +1233,17 @@ module MachO # @return [Fixnum] the version packed as a24.b10.c10.d10.e10 attr_reader :version - FORMAT = "L=2Q=1" + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2Q=1".freeze + + # @see MachOStructure::SIZEOF + # @api private SIZEOF = 16 # @api private - def initialize(raw_data, offset, cmd, cmdsize, version) - super(raw_data, offset, cmd, cmdsize) + def initialize(view, cmd, cmdsize, version) + super(view, cmd, cmdsize) @version = version end @@ -1008,4 +1259,94 @@ module MachO segs.join(".") end end + + # An obsolete load command containing the offset and size of the (GNU style) + # symbol table information. Corresponds to LC_SYMSEG. + class SymsegCommand < LoadCommand + # @return [Fixnum] the offset to the symbol segment + attr_reader :offset + + # @return [Fixnum] the size of the symbol segment in bytes + attr_reader :size + + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=4".freeze + + # @see MachOStructure::SIZEOF + # @api private + SIZEOF = 16 + + # @api private + def initialize(view, cmd, cmdsize, offset, size) + super(view, cmd, cmdsize) + @offset = offset + @size = size + end + end + + # An obsolete load command containing a free format string table. Each string + # is null-terminated and the command is zero-padded to a multiple of 4. + # Corresponds to LC_IDENT. + class IdentCommand < LoadCommand + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=2".freeze + + # @see MachOStructure::SIZEOF + # @api private + SIZEOF = 8 + end + + # An obsolete load command containing the path to a file to be loaded into + # memory. Corresponds to LC_FVMFILE. + class FvmfileCommand < LoadCommand + # @return [MachO::LoadCommand::LCStr] the pathname of the file being loaded + attr_reader :name + + # @return [Fixnum] the virtual address being loaded at + attr_reader :header_addr + + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=4".freeze + + # @see MachOStructure::SIZEOF + # @api private + SIZEOF = 16 + + def initialize(view, cmd, cmdsize, name, header_addr) + super(view, cmd, cmdsize) + @name = LCStr.new(self, name) + @header_addr = header_addr + end + end + + # An obsolete load command containing the path to a library to be loaded into + # memory. Corresponds to LC_LOADFVMLIB and LC_IDFVMLIB. + class FvmlibCommand < LoadCommand + # @return [MachO::LoadCommand::LCStr] the library's target pathname + attr_reader :name + + # @return [Fixnum] the library's minor version number + attr_reader :minor_version + + # @return [Fixnum] the library's header address + attr_reader :header_addr + + # @see MachOStructure::FORMAT + # @api private + FORMAT = "L=5".freeze + + # @see MachOStructure::SIZEOF + # @api private + SIZEOF = 20 + + def initialize(view, cmd, cmdsize, name, minor_version, header_addr) + super(view, cmd, cmdsize) + @name = LCStr.new(self, name) + @minor_version = minor_version + @header_addr = header_addr + end + end end diff --git a/Library/Homebrew/vendor/macho/macho/macho_file.rb b/Library/Homebrew/vendor/macho/macho/macho_file.rb index dc8d6fc250..8ece626721 100644 --- a/Library/Homebrew/vendor/macho/macho/macho_file.rb +++ b/Library/Homebrew/vendor/macho/macho/macho_file.rb @@ -16,6 +16,7 @@ module MachO attr_reader :header # @return [Array<MachO::LoadCommand>] an array of the file's load commands + # @note load commands are provided in order of ascending offset. attr_reader :load_commands # Creates a new MachOFile instance from a binary string. @@ -32,20 +33,20 @@ module MachO # @param filename [String] the Mach-O file to load from # @raise [ArgumentError] if the given file does not exist def initialize(filename) - raise ArgumentError.new("#{filename}: no such file") unless File.file?(filename) + raise ArgumentError, "#{filename}: no such file" unless File.file?(filename) @filename = filename - @raw_data = File.open(@filename, "rb") { |f| f.read } - @header = get_mach_header - @load_commands = get_load_commands + @raw_data = File.open(@filename, "rb", &:read) + populate_fields end + # Initializes a new MachOFile instance from a binary string. + # @see MachO::MachOFile.new_from_bin # @api private def initialize_from_bin(bin) @filename = nil @raw_data = bin - @header = get_mach_header - @load_commands = get_load_commands + populate_fields end # The file's raw Mach-O data. @@ -56,12 +57,17 @@ module MachO # @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise def magic32? - MachO.magic32?(header.magic) + Utils.magic32?(header.magic) end # @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise def magic64? - MachO.magic64?(header.magic) + Utils.magic64?(header.magic) + end + + # @return [Fixnum] the file's internal alignment + def alignment + magic32? ? 4 : 8 end # @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise @@ -124,7 +130,7 @@ module MachO MH_MAGICS[magic] end - # @return [String] a string representation of the Mach-O's filetype + # @return [Symbol] a string representation of the Mach-O's filetype def filetype MH_FILETYPES[header.filetype] end @@ -164,7 +170,109 @@ module MachO load_commands.select { |lc| lc.type == name.to_sym } end - alias :[] :command + alias [] command + + # Inserts a load command at the given offset. + # @param offset [Fixnum] the offset to insert at + # @param lc [MachO::LoadCommand] the load command to insert + # @param options [Hash] + # @option options [Boolean] :repopulate (true) whether or not to repopulate + # the instance fields + # @raise [MachO::OffsetInsertionError] if the offset is not in the load command region + # @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer + # @note Calling this method with an arbitrary offset in the load command + # region **will leave the object in an inconsistent state**. + def insert_command(offset, lc, options = {}) + context = LoadCommand::SerializationContext.context_for(self) + cmd_raw = lc.serialize(context) + + if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff + raise OffsetInsertionError, offset + end + + new_sizeofcmds = sizeofcmds + cmd_raw.bytesize + + if header.class.bytesize + new_sizeofcmds > low_fileoff + raise HeaderPadError, @filename + end + + # update Mach-O header fields to account for inserted load command + update_ncmds(ncmds + 1) + update_sizeofcmds(new_sizeofcmds) + + @raw_data.insert(offset, cmd_raw) + @raw_data.slice!(header.class.bytesize + new_sizeofcmds, cmd_raw.bytesize) + + populate_fields if options.fetch(:repopulate, true) + end + + # Replace a load command with another command in the Mach-O, preserving location. + # @param old_lc [MachO::LoadCommand] the load command being replaced + # @param new_lc [MachO::LoadCommand] the load command being added + # @return [void] + # @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer + # @see {#insert_command} + # @note This is public, but methods like {#dylib_id=} should be preferred. + def replace_command(old_lc, new_lc) + context = LoadCommand::SerializationContext.context_for(self) + cmd_raw = new_lc.serialize(context) + new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize + if header.class.bytesize + new_sizeofcmds > low_fileoff + raise HeaderPadError, @filename + end + + delete_command(old_lc) + insert_command(old_lc.view.offset, new_lc) + end + + # Appends a new load command to the Mach-O. + # @param lc [MachO::LoadCommand] the load command being added + # @param options [Hash] + # @option options [Boolean] :repopulate (true) whether or not to repopulate + # the instance fields + # @return [void] + # @see {#insert_command} + # @note This is public, but methods like {#add_rpath} should be preferred. + # Setting `repopulate` to false **will leave the instance in an + # inconsistent state** unless {#populate_fields} is called **immediately** + # afterwards. + def add_command(lc, options = {}) + insert_command(header.class.bytesize + sizeofcmds, lc, options) + end + + # Delete a load command from the Mach-O. + # @param lc [MachO::LoadCommand] the load command being deleted + # @param options [Hash] + # @option options [Boolean] :repopulate (true) whether or not to repopulate + # the instance fields + # @return [void] + # @note This is public, but methods like {#delete_rpath} should be preferred. + # Setting `repopulate` to false **will leave the instance in an + # inconsistent state** unless {#populate_fields} is called **immediately** + # afterwards. + def delete_command(lc, options = {}) + @raw_data.slice!(lc.view.offset, lc.cmdsize) + + # update Mach-O header fields to account for deleted load command + update_ncmds(ncmds - 1) + update_sizeofcmds(sizeofcmds - lc.cmdsize) + + # pad the space after the load commands to preserve offsets + null_pad = "\x00" * lc.cmdsize + @raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad) + + populate_fields if options.fetch(:repopulate, true) + end + + # Populate the instance's fields with the raw Mach-O data. + # @return [void] + # @note This method is public, but should (almost) never need to be called. + # The exception to this rule is when methods like {#add_command} and + # {#delete_command} have been called with `repopulate = false`. + def populate_fields + @header = populate_mach_header + @load_commands = populate_load_commands + end # All load commands responsible for loading dylibs. # @return [Array<MachO::DylibCommand>] an array of DylibCommands @@ -188,9 +296,7 @@ module MachO # file.dylib_id # => 'libBar.dylib' # @return [String, nil] the Mach-O's dylib ID def dylib_id - if !dylib? - return nil - end + return unless dylib? dylib_id_cmd = command(:LC_ID_DYLIB).first @@ -199,25 +305,30 @@ module MachO # Changes the Mach-O's dylib ID to `new_id`. Does nothing if not a dylib. # @example - # file.dylib_id = "libFoo.dylib" + # file.change_dylib_id("libFoo.dylib") # @param new_id [String] the dylib's new ID + # @param _options [Hash] # @return [void] # @raise [ArgumentError] if `new_id` is not a String - def dylib_id=(new_id) - if !new_id.is_a?(String) - raise ArgumentError.new("argument must be a String") - end + # @note `_options` is currently unused and is provided for signature + # compatibility with {MachO::FatFile#change_dylib_id} + def change_dylib_id(new_id, _options = {}) + raise ArgumentError, "new ID must be a String" unless new_id.is_a?(String) + return unless dylib? - if !dylib? - return nil - end + old_lc = command(:LC_ID_DYLIB).first + raise DylibIdMissingError unless old_lc - dylib_cmd = command(:LC_ID_DYLIB).first - old_id = dylib_id + new_lc = LoadCommand.create(:LC_ID_DYLIB, new_id, + old_lc.timestamp, + old_lc.current_version, + old_lc.compatibility_version) - set_name_in_dylib(dylib_cmd, old_id, new_id) + replace_command(old_lc, new_lc) end + alias dylib_id= change_dylib_id + # All shared libraries linked to the Mach-O. # @return [Array<String>] an array of all shared libraries def linked_dylibs @@ -233,16 +344,24 @@ module MachO # file.change_install_name("/usr/lib/libWhatever.dylib", "/usr/local/lib/libWhatever2.dylib") # @param old_name [String] the shared library's old name # @param new_name [String] the shared library's new name + # @param _options [Hash] # @return [void] # @raise [MachO::DylibUnknownError] if no shared library has the old name - def change_install_name(old_name, new_name) - dylib_cmd = dylib_load_commands.find { |d| d.name.to_s == old_name } - raise DylibUnknownError.new(old_name) if dylib_cmd.nil? + # @note `_options` is currently unused and is provided for signature + # compatibility with {MachO::FatFile#change_install_name} + def change_install_name(old_name, new_name, _options = {}) + old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name } + raise DylibUnknownError, old_name if old_lc.nil? - set_name_in_dylib(dylib_cmd, old_name, new_name) + new_lc = LoadCommand.create(old_lc.type, new_name, + old_lc.timestamp, + old_lc.current_version, + old_lc.compatibility_version) + + replace_command(old_lc, new_lc) end - alias :change_dylib :change_install_name + alias change_dylib change_install_name # All runtime paths searched by the dynamic linker for the Mach-O. # @return [Array<String>] an array of all runtime paths @@ -255,44 +374,70 @@ module MachO # file.change_rpath("/usr/lib", "/usr/local/lib") # @param old_path [String] the old runtime path # @param new_path [String] the new runtime path + # @param _options [Hash] # @return [void] # @raise [MachO::RpathUnknownError] if no such old runtime path exists - # @api private - def change_rpath(old_path, new_path) - rpath_cmd = command(:LC_RPATH).find { |r| r.path.to_s == old_path } - raise RpathUnknownError.new(old_path) if rpath_cmd.nil? + # @raise [MachO::RpathExistsError] if the new runtime path already exists + # @note `_options` is currently unused and is provided for signature + # compatibility with {MachO::FatFile#change_rpath} + def change_rpath(old_path, new_path, _options = {}) + old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path } + raise RpathUnknownError, old_path if old_lc.nil? + raise RpathExistsError, new_path if rpaths.include?(new_path) + + new_lc = LoadCommand.create(:LC_RPATH, new_path) + + delete_rpath(old_path) + insert_command(old_lc.view.offset, new_lc) + end - set_path_in_rpath(rpath_cmd, old_path, new_path) + # Add the given runtime path to the Mach-O. + # @example + # file.rpaths # => ["/lib"] + # file.add_rpath("/usr/lib") + # file.rpaths # => ["/lib", "/usr/lib"] + # @param path [String] the new runtime path + # @param _options [Hash] + # @return [void] + # @raise [MachO::RpathExistsError] if the runtime path already exists + # @note `_options` is currently unused and is provided for signature + # compatibility with {MachO::FatFile#add_rpath} + def add_rpath(path, _options = {}) + raise RpathExistsError, path if rpaths.include?(path) + + rpath_cmd = LoadCommand.create(:LC_RPATH, path) + add_command(rpath_cmd) + end + + # Delete the given runtime path from the Mach-O. + # @example + # file.rpaths # => ["/lib"] + # file.delete_rpath("/lib") + # file.rpaths # => [] + # @param path [String] the runtime path to delete + # @param _options [Hash] + # @return void + # @raise [MachO::RpathUnknownError] if no such runtime path exists + # @note `_options` is currently unused and is provided for signature + # compatibility with {MachO::FatFile#delete_rpath} + def delete_rpath(path, _options = {}) + rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path } + raise RpathUnknownError, path if rpath_cmds.empty? + + # delete the commands in reverse order, offset descending. this + # allows us to defer (expensive) field population until the very end + rpath_cmds.reverse_each { |cmd| delete_command(cmd, :repopulate => false) } + + populate_fields end # All sections of the segment `segment`. # @param segment [MachO::SegmentCommand, MachO::SegmentCommand64] the segment being inspected # @return [Array<MachO::Section>] if the Mach-O is 32-bit # @return [Array<MachO::Section64>] if the Mach-O is 64-bit + # @deprecated use {MachO::SegmentCommand#sections} instead def sections(segment) - sections = [] - - if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64) - raise ArgumentError.new("not a valid segment") - end - - if segment.nsects.zero? - return sections - end - - offset = segment.offset + segment.class.bytesize - - segment.nsects.times do - if segment.is_a? SegmentCommand - sections << Section.new_from_bin(endianness, @raw_data.slice(offset, Section.bytesize)) - offset += Section.bytesize - else - sections << Section64.new_from_bin(endianness, @raw_data.slice(offset, Section64.bytesize)) - offset += Section64.bytesize - end - end - - sections + segment.sections end # Write all Mach-O data to the given filename. @@ -308,7 +453,7 @@ module MachO # @note Overwrites all data in the file! def write! if @filename.nil? - raise MachOError.new("cannot write to a default file when initialized from a binary string") + raise MachOError, "cannot write to a default file when initialized from a binary string" else File.open(@filename, "wb") { |f| f.write(@raw_data) } end @@ -320,13 +465,13 @@ module MachO # @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 + # @api private + def populate_mach_header # the smallest Mach-O header is 28 bytes - raise TruncatedFileError.new if @raw_data.size < 28 + raise TruncatedFileError if @raw_data.size < 28 - magic = get_and_check_magic - mh_klass = MachO.magic32?(magic) ? MachHeader : MachHeader64 + magic = populate_and_check_magic + mh_klass = Utils.magic32?(magic) ? MachHeader : MachHeader64 mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize]) check_cputype(mh.cputype) @@ -340,14 +485,14 @@ module MachO # @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_and_check_magic + # @api private + def populate_and_check_magic magic = @raw_data[0..3].unpack("N").first - raise MagicError.new(magic) unless MachO.magic?(magic) - raise FatBinaryError.new if MachO.fat_magic?(magic) + raise MagicError, magic unless Utils.magic?(magic) + raise FatBinaryError if Utils.fat_magic?(magic) - @endianness = MachO.little_magic?(magic) ? :little : :big + @endianness = Utils.little_magic?(magic) ? :little : :big magic end @@ -355,47 +500,48 @@ module MachO # Check the file's CPU type. # @param cputype [Fixnum] the CPU type # @raise [MachO::CPUTypeError] if the CPU type is unknown - # @private + # @api private def check_cputype(cputype) - raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype) + raise CPUTypeError, cputype unless CPU_TYPES.key?(cputype) end # Check the file's CPU type/subtype pair. # @param cpusubtype [Fixnum] the CPU subtype # @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown - # @private + # @api private def check_cpusubtype(cputype, cpusubtype) - # Only check sub-type w/o capability bits (see `get_mach_header`). + # Only check sub-type w/o capability bits (see `populate_mach_header`). raise CPUSubtypeError.new(cputype, cpusubtype) unless CPU_SUBTYPES[cputype].key?(cpusubtype) end # Check the file's type. # @param filetype [Fixnum] the file type # @raise [MachO::FiletypeError] if the file type is unknown - # @private + # @api private def check_filetype(filetype) - raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype) + raise FiletypeError, filetype unless MH_FILETYPES.key?(filetype) end # All load commands in the file. # @return [Array<MachO::LoadCommand>] an array of load commands # @raise [MachO::LoadCommandError] if an unknown load command is encountered - # @private - def get_load_commands + # @api private + def populate_load_commands offset = header.class.bytesize load_commands = [] header.ncmds.times do - fmt = (endianness == :little) ? "L<" : "L>" + fmt = Utils.specialize_format("L=", endianness) cmd = @raw_data.slice(offset, 4).unpack(fmt).first cmd_sym = LOAD_COMMANDS[cmd] - raise LoadCommandError.new(cmd) if cmd_sym.nil? + raise LoadCommandError, cmd if cmd_sym.nil? # why do I do this? i don't like declaring constants below # classes, and i need them to resolve... - klass = MachO.const_get "#{LC_STRUCTURES[cmd_sym]}" - command = klass.new_from_bin(@raw_data, endianness, offset, @raw_data.slice(offset, klass.bytesize)) + klass = MachO.const_get LC_STRUCTURES[cmd_sym] + view = MachOView.new(@raw_data, endianness, offset) + command = klass.new_from_bin(view) load_commands << command offset += command.cmdsize @@ -404,108 +550,44 @@ module MachO load_commands end - # Updates the size of all load commands in the raw data. - # @param size [Fixnum] the new size, in bytes - # @return [void] - # @private - def set_sizeofcmds(size) - fmt = (endianness == :little) ? "L<" : "L>" - new_size = [size].pack(fmt) - @raw_data[20..23] = new_size - end - - # Updates the `name` field in a DylibCommand. - # @param dylib_cmd [MachO::DylibCommand] the dylib command - # @param old_name [String] the old dylib name - # @param new_name [String] the new dylib name - # @return [void] - # @private - def set_name_in_dylib(dylib_cmd, old_name, new_name) - set_lc_str_in_cmd(dylib_cmd, dylib_cmd.name, old_name, new_name) - end - - # Updates the `path` field in an RpathCommand. - # @param rpath_cmd [MachO::RpathCommand] the rpath command - # @param old_path [String] the old runtime name - # @param new_path [String] the new runtime name - # @return [void] - # @private - def set_path_in_rpath(rpath_cmd, old_path, new_path) - set_lc_str_in_cmd(rpath_cmd, rpath_cmd.path, old_path, new_path) - end - - # Updates a generic LCStr field in any LoadCommand. - # @param cmd [MachO::LoadCommand] the load command - # @param lc_str [MachO::LoadCommand::LCStr] the load command string - # @param old_str [String] the old string - # @param new_str [String] the new string - # @raise [MachO::HeaderPadError] if the new name exceeds the header pad buffer - # @private - def set_lc_str_in_cmd(cmd, lc_str, old_str, new_str) - if magic32? - cmd_round = 4 - else - cmd_round = 8 - end - - new_sizeofcmds = header.sizeofcmds - old_str = old_str.dup - new_str = new_str.dup - - old_pad = MachO.round(old_str.size + 1, cmd_round) - old_str.size - new_pad = MachO.round(new_str.size + 1, cmd_round) - new_str.size - - # pad the old and new IDs with null bytes to meet command bounds - old_str << "\x00" * old_pad - new_str << "\x00" * new_pad - - # calculate the new size of the cmd and sizeofcmds in MH - new_size = cmd.class.bytesize + new_str.size - new_sizeofcmds += new_size - cmd.cmdsize - - low_fileoff = @raw_data.size + # The low file offset (offset to first section data). + # @return [Fixnum] the offset + # @api private + def low_fileoff + offset = @raw_data.size - # calculate the low file offset (offset to first section data) segments.each do |seg| - sections(seg).each do |sect| - next if sect.size == 0 + seg.sections.each do |sect| + next if sect.empty? next if sect.flag?(:S_ZEROFILL) next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL) - next unless sect.offset < low_fileoff + next unless sect.offset < offset - low_fileoff = sect.offset + offset = sect.offset end end - if new_sizeofcmds + header.class.bytesize > low_fileoff - raise HeaderPadError.new(@filename) - end - - # update sizeofcmds in mach_header - set_sizeofcmds(new_sizeofcmds) - - # update cmdsize in the cmd - fmt = (endianness == :little) ? "L<" : "L>" - @raw_data[cmd.offset + 4, 4] = [new_size].pack(fmt) - - # delete the old str - @raw_data.slice!(cmd.offset + lc_str.to_i...cmd.offset + cmd.class.bytesize + old_str.size) - - # insert the new str - @raw_data.insert(cmd.offset + lc_str.to_i, new_str) - - # pad/unpad after new_sizeofcmds until offsets are corrected - null_pad = old_str.size - new_str.size + offset + end - if null_pad < 0 - @raw_data.slice!(new_sizeofcmds + header.class.bytesize, null_pad.abs) - else - @raw_data.insert(new_sizeofcmds + header.class.bytesize, "\x00" * null_pad) - end + # Updates the number of load commands in the raw data. + # @param ncmds [Fixnum] the new number of commands + # @return [void] + # @api private + def update_ncmds(ncmds) + fmt = Utils.specialize_format("L=", endianness) + ncmds_raw = [ncmds].pack(fmt) + @raw_data[16..19] = ncmds_raw + end - # synchronize fields with the raw data - @header = get_mach_header - @load_commands = get_load_commands + # Updates the size of all load commands in the raw data. + # @param size [Fixnum] the new size, in bytes + # @return [void] + # @api private + def update_sizeofcmds(size) + fmt = Utils.specialize_format("L=", endianness) + size_raw = [size].pack(fmt) + @raw_data[20..23] = size_raw end end end diff --git a/Library/Homebrew/vendor/macho/macho/open.rb b/Library/Homebrew/vendor/macho/macho/open.rb index ad1b02bb21..103f617410 100644 --- a/Library/Homebrew/vendor/macho/macho/open.rb +++ b/Library/Homebrew/vendor/macho/macho/open.rb @@ -7,17 +7,17 @@ module MachO # @raise [MachO::TruncatedFileError] if the file is too small to have a valid header # @raise [MachO::MagicError] if the file's magic is not valid Mach-O magic def self.open(filename) - raise ArgumentError.new("#{filename}: no such file") unless File.file?(filename) - raise TruncatedFileError.new unless File.stat(filename).size >= 4 + raise ArgumentError, "#{filename}: no such file" unless File.file?(filename) + raise TruncatedFileError unless File.stat(filename).size >= 4 magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first - if MachO.fat_magic?(magic) + if Utils.fat_magic?(magic) file = FatFile.new(filename) - elsif MachO.magic?(magic) + elsif Utils.magic?(magic) file = MachOFile.new(filename) else - raise MagicError.new(magic) + raise MagicError, magic end file diff --git a/Library/Homebrew/vendor/macho/macho/sections.rb b/Library/Homebrew/vendor/macho/macho/sections.rb index 3ea1e3fa4b..bd6218bb5a 100644 --- a/Library/Homebrew/vendor/macho/macho/sections.rb +++ b/Library/Homebrew/vendor/macho/macho/sections.rb @@ -45,7 +45,7 @@ module MachO :S_ATTR_DEBUG => 0x02000000, :S_ATTR_SOME_INSTRUCTIONS => 0x00000400, :S_ATTR_EXT_RELOC => 0x00000200, - :S_ATTR_LOC_RELOC => 0x00000100 + :S_ATTR_LOC_RELOC => 0x00000100, }.freeze # association of section name symbols to names @@ -62,7 +62,7 @@ module MachO :SECT_OBJC_STRINGS => "__selector_strs", :SECT_OBJC_REFS => "__selector_refs", :SECT_ICON_HEADER => "__header", - :SECT_ICON_TIFF => "__tiff" + :SECT_ICON_TIFF => "__tiff", }.freeze # Represents a section of a segment for 32-bit architectures. @@ -91,7 +91,7 @@ module MachO # @return [Fixnum] the number of relocation entries attr_reader :nreloc - # @return [Fixnum] flags for type and addrributes of the section + # @return [Fixnum] flags for type and attributes of the section attr_reader :flags # @return [void] reserved (for offset or index) @@ -100,12 +100,15 @@ module MachO # @return [void] reserved (for count or sizeof) attr_reader :reserved2 - FORMAT = "a16a16L=9" + # @see MachOStructure::FORMAT + FORMAT = "a16a16L=9".freeze + + # @see MachOStructure::SIZEOF SIZEOF = 68 # @api private def initialize(sectname, segname, addr, size, offset, align, reloff, - nreloc, flags, reserved1, reserved2) + nreloc, flags, reserved1, reserved2) @sectname = sectname @segname = segname @addr = addr @@ -121,12 +124,17 @@ module MachO # @return [String] the section's name, with any trailing NULL characters removed def section_name - @sectname.delete("\x00") + sectname.delete("\x00") end # @return [String] the parent segment's name, with any trailing NULL characters removed def segment_name - @segname.delete("\x00") + segname.delete("\x00") + end + + # @return [Boolean] true if the section has no contents (i.e, `size` is 0) + def empty? + size.zero? end # @example @@ -145,12 +153,15 @@ module MachO # @return [void] reserved attr_reader :reserved3 - FORMAT = "a16a16Q=2L=8" + # @see MachOStructure::FORMAT + FORMAT = "a16a16Q=2L=8".freeze + + # @see MachOStructure::SIZEOF SIZEOF = 80 # @api private def initialize(sectname, segname, addr, size, offset, align, reloff, - nreloc, flags, reserved1, reserved2, reserved3) + nreloc, flags, reserved1, reserved2, reserved3) super(sectname, segname, addr, size, offset, align, reloff, nreloc, flags, reserved1, reserved2) @reserved3 = reserved3 diff --git a/Library/Homebrew/vendor/macho/macho/structure.rb b/Library/Homebrew/vendor/macho/macho/structure.rb index aaa2ada275..43f7bc84ed 100644 --- a/Library/Homebrew/vendor/macho/macho/structure.rb +++ b/Library/Homebrew/vendor/macho/macho/structure.rb @@ -3,9 +3,13 @@ module MachO # @abstract class MachOStructure # The String#unpack format of the data structure. - FORMAT = "" + # @return [String] the unpacking format + # @api private + FORMAT = "".freeze # The size of the data structure, in bytes. + # @return [Fixnum] the size, in bytes + # @api private SIZEOF = 0 # @return [Fixnum] the size, in bytes, of the represented structure. @@ -13,26 +17,14 @@ module MachO self::SIZEOF end - # @param endianness [Symbol] either :big or :little + # @param endianness [Symbol] either `:big` or `:little` # @param bin [String] the string to be unpacked into the new structure # @return [MachO::MachOStructure] a new MachOStructure initialized with `bin` # @api private def self.new_from_bin(endianness, bin) - format = specialize_format(self::FORMAT, endianness) - - self.new(*bin.unpack(format)) - end - - private + format = Utils.specialize_format(self::FORMAT, endianness) - # Convert an abstract (native-endian) String#unpack format to big or little. - # @param format [String] the format string being converted - # @param endianness [Symbol] either :big or :little - # @return [String] the converted string - # @api private - def self.specialize_format(format, endianness) - modifier = (endianness == :big) ? ">" : "<" - format.tr("=", modifier) + new(*bin.unpack(format)) end end end diff --git a/Library/Homebrew/vendor/macho/macho/tools.rb b/Library/Homebrew/vendor/macho/macho/tools.rb index fe3da455b4..f34d75dc14 100644 --- a/Library/Homebrew/vendor/macho/macho/tools.rb +++ b/Library/Homebrew/vendor/macho/macho/tools.rb @@ -12,12 +12,14 @@ module MachO # Changes the dylib ID of a Mach-O or Fat binary, overwriting the source file. # @param filename [String] the Mach-O or Fat binary being modified # @param new_id [String] the new dylib ID for the binary + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if the change cannot be performed # @return [void] - # @todo unstub for fat files - def self.change_dylib_id(filename, new_id) + def self.change_dylib_id(filename, new_id, options = {}) file = MachO.open(filename) - file.dylib_id = new_id + file.change_dylib_id(new_id, options) file.write! end @@ -25,12 +27,14 @@ module MachO # @param filename [String] the Mach-O or Fat binary being modified # @param old_name [String] the old shared library name # @param new_name [String] the new shared library name + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if the change cannot be performed # @return [void] - # @todo unstub for fat files - def self.change_install_name(filename, old_name, new_name) + def self.change_install_name(filename, old_name, new_name, options = {}) file = MachO.open(filename) - file.change_install_name(old_name, new_name) + file.change_install_name(old_name, new_name, options) file.write! end @@ -38,28 +42,43 @@ module MachO # @param filename [String] the Mach-O or Fat binary being modified # @param old_path [String] the old runtime path # @param new_path [String] the new runtime path + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if the change cannot be performed # @return [void] - # @todo unstub - def self.change_rpath(filename, old_path, new_path) - raise UnimplementedError.new("changing rpaths in a Mach-O") + def self.change_rpath(filename, old_path, new_path, options = {}) + file = MachO.open(filename) + + file.change_rpath(old_path, new_path, options) + file.write! end # Add a runtime path to a Mach-O or Fat binary, overwriting the source file. # @param filename [String] the Mach-O or Fat binary being modified # @param new_path [String] the new runtime path + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if the change cannot be performed # @return [void] - # @todo unstub - def self.add_rpath(filename, new_path) - raise UnimplementedError.new("adding rpaths to a Mach-O") + def self.add_rpath(filename, new_path, options = {}) + file = MachO.open(filename) + + file.add_rpath(new_path, options) + file.write! end # Delete a runtime path from a Mach-O or Fat binary, overwriting the source file. # @param filename [String] the Mach-O or Fat binary being modified # @param old_path [String] the old runtime path + # @param options [Hash] + # @option options [Boolean] :strict (true) whether or not to fail loudly + # with an exception if the change cannot be performed # @return [void] - # @todo unstub - def self.delete_rpath(filename, old_path) - raise UnimplementedError.new("removing rpaths from a Mach-O") + def self.delete_rpath(filename, old_path, options = {}) + file = MachO.open(filename) + + file.delete_rpath(old_path, options) + file.write! end end end diff --git a/Library/Homebrew/vendor/macho/macho/utils.rb b/Library/Homebrew/vendor/macho/macho/utils.rb index 102f1e57d4..deb4c9c665 100644 --- a/Library/Homebrew/vendor/macho/macho/utils.rb +++ b/Library/Homebrew/vendor/macho/macho/utils.rb @@ -1,48 +1,96 @@ module MachO - # @param value [Fixnum] the number being rounded - # @param round [Fixnum] the number being rounded with - # @return [Fixnum] the next number >= `value` such that `round` is its divisor - # @see http://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c - def self.round(value, round) - round -= 1 - value += round - value &= ~round - value - end + # A collection of utility functions used throughout ruby-macho. + module Utils + # Rounds a value to the next multiple of the given round. + # @param value [Fixnum] the number being rounded + # @param round [Fixnum] the number being rounded with + # @return [Fixnum] the rounded value + # @see http://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c + def self.round(value, round) + round -= 1 + value += round + value &= ~round + value + end - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid Mach-O magic number, false otherwise - def self.magic?(num) - MH_MAGICS.has_key?(num) - end + # Returns the number of bytes needed to pad the given size to the given alignment. + # @param size [Fixnum] the unpadded size + # @param alignment [Fixnum] the number to alignment the size with + # @return [Fixnum] the number of pad bytes required + def self.padding_for(size, alignment) + round(size, alignment) - size + end - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid Fat magic number, false otherwise - def self.fat_magic?(num) - num == FAT_MAGIC - end + # Converts an abstract (native-endian) String#unpack format to big or little. + # @param format [String] the format string being converted + # @param endianness [Symbol] either `:big` or `:little` + # @return [String] the converted string + def self.specialize_format(format, endianness) + modifier = endianness == :big ? ">" : "<" + format.tr("=", modifier) + end - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise - def self.magic32?(num) - num == MH_MAGIC || num == MH_CIGAM - end + # Packs tagged strings into an aligned payload. + # @param fixed_offset [Fixnum] the baseline offset for the first packed string + # @param alignment [Fixnum] the alignment value to use for packing + # @param strings [Hash] the labeled strings to pack + # @return [Array<String, Hash>] the packed string and labeled offsets + def self.pack_strings(fixed_offset, alignment, strings = {}) + offsets = {} + next_offset = fixed_offset + payload = "" - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise - def self.magic64?(num) - num == MH_MAGIC_64 || num == MH_CIGAM_64 - end + strings.each do |key, string| + offsets[key] = next_offset + payload << string + payload << "\x00" + next_offset += string.bytesize + 1 + end - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise - def self.little_magic?(num) - num == MH_CIGAM || num == MH_CIGAM_64 - end + payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment) + [payload, offsets] + end + + # Compares the given number to valid Mach-O magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid Mach-O magic number, false otherwise + def self.magic?(num) + MH_MAGICS.key?(num) + end + + # Compares the given number to valid Fat magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid Fat magic number, false otherwise + def self.fat_magic?(num) + num == FAT_MAGIC + end + + # Compares the given number to valid 32-bit Mach-O magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise + def self.magic32?(num) + num == MH_MAGIC || num == MH_CIGAM + end + + # Compares the given number to valid 64-bit Mach-O magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise + def self.magic64?(num) + num == MH_MAGIC_64 || num == MH_CIGAM_64 + end + + # Compares the given number to valid little-endian magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise + def self.little_magic?(num) + num == MH_CIGAM || num == MH_CIGAM_64 + end - # @param num [Fixnum] the number being checked - # @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise - def self.big_magic?(num) - num == MH_CIGAM || num == MH_CIGAM_64 + # Compares the given number to valid big-endian magic numbers. + # @param num [Fixnum] the number being checked + # @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise + def self.big_magic?(num) + num == MH_CIGAM || num == MH_CIGAM_64 + end end end diff --git a/Library/Homebrew/vendor/macho/macho/view.rb b/Library/Homebrew/vendor/macho/macho/view.rb new file mode 100644 index 0000000000..9586e73bee --- /dev/null +++ b/Library/Homebrew/vendor/macho/macho/view.rb @@ -0,0 +1,23 @@ +module MachO + # A representation of some unspecified Mach-O data. + class MachOView + # @return [String] the raw Mach-O data + attr_reader :raw_data + + # @return [Symbol] the endianness of the data (`:big` or `:little`) + attr_reader :endianness + + # @return [Fixnum] the offset of the relevant data (in {#raw_data}) + attr_reader :offset + + # Creates a new MachOView. + # @param raw_data [String] the raw Mach-O data + # @param endianness [Symbol] the endianness of the data + # @param offset [Fixnum] the offset of the relevant data + def initialize(raw_data, endianness, offset) + @raw_data = raw_data + @endianness = endianness + @offset = offset + end + end +end -- GitLab