vendor/macho: update to 0.2.2-39-ge2fbedc9

This commit is contained in:
Martin Afanasjew 2016-02-18 18:04:41 +01:00
parent 2496bdf280
commit 01d642f150
7 changed files with 130 additions and 91 deletions

View File

@ -3,7 +3,7 @@ Vendored Dependencies
* [okjson](https://github.com/kr/okjson), version 43. * [okjson](https://github.com/kr/okjson), version 43.
* [ruby-macho](https://github.com/woodruffw/ruby-macho), version 0.2.2. * [ruby-macho](https://github.com/Homebrew/ruby-macho), version 0.2.2-39-ge2fbedc9.
## Licenses: ## Licenses:

View File

@ -3,14 +3,36 @@ module MachO
class MachOError < RuntimeError class MachOError < RuntimeError
end end
# Raised when a file is not a Mach-O.
class NotAMachOError < MachOError
# @param error [String] the error in question
def initialize(error)
super error
end
end
# Raised when a file is too short to be a valid Mach-O file.
class TruncatedFileError < NotAMachOError
def initialize
super "File is too short to be a valid Mach-O"
end
end
# Raised when a file's magic bytes are not valid Mach-O magic. # Raised when a file's magic bytes are not valid Mach-O magic.
class MagicError < MachOError class MagicError < NotAMachOError
# @param num [Fixnum] the unknown number # @param num [Fixnum] the unknown number
def initialize(num) def initialize(num)
super "Unrecognized Mach-O magic: 0x#{"%02x" % num}" super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
end end
end end
# Raised when a file is a Java classfile instead of a fat Mach-O.
class JavaClassFileError < NotAMachOError
def initialize
super "File is a Java class file"
end
end
# Raised when a fat binary is loaded with MachOFile. # Raised when a fat binary is loaded with MachOFile.
class FatBinaryError < MachOError class FatBinaryError < MachOError
def initialize def initialize
@ -82,4 +104,12 @@ module MachO
super "No such runtime path: #{path}" super "No such runtime path: #{path}"
end end
end end
# Raised whenever unfinished code is called.
class UnimplementedError < MachOError
# @param thing [String] the thing that is unimplemented
def initialize(thing)
super "Unimplemented: #{thing}"
end
end
end end

View File

@ -13,6 +13,16 @@ module MachO
# @return [Array<MachO::MachOFile>] an array of Mach-O binaries # @return [Array<MachO::MachOFile>] an array of Mach-O binaries
attr_reader :machos attr_reader :machos
# Creates a new FatFile instance from a binary string.
# @param bin [String] a binary string containing raw Mach-O data
# @return [MachO::FatFile] a new FatFile
def self.new_from_bin(bin)
instance = allocate
instance.initialize_from_bin(bin)
instance
end
# Creates a new FatFile from the given filename. # Creates a new FatFile from the given filename.
# @param filename [String] the fat file to load from # @param filename [String] the fat file to load from
# @raise [ArgumentError] if the given filename does not exist # @raise [ArgumentError] if the given filename does not exist
@ -26,6 +36,15 @@ module MachO
@machos = get_machos @machos = get_machos
end end
# @api private
def initialize_from_bin(bin)
@filename = nil
@raw_data = bin
@header = get_fat_header
@fat_archs = get_fat_archs
@machos = get_machos
end
# The file's raw fat data. # The file's raw fat data.
# @return [String] the raw fat data # @return [String] the raw fat data
def serialize def serialize
@ -131,8 +150,10 @@ module MachO
# All shared libraries linked to the file's Mach-Os. # All shared libraries linked to the file's Mach-Os.
# @return [Array<String>] an array of all shared libraries # @return [Array<String>] an array of all shared libraries
def linked_dylibs def linked_dylibs
# can machos inside fat binaries have different dylibs? # Individual architectures in a fat binary can link to different subsets
machos.flat_map(&:linked_dylibs).uniq # of libraries, but at this point we want to have the full picture, i.e.
# the union of all libraries used by all architectures.
machos.map(&:linked_dylibs).flatten.uniq
end end
# Changes all dependent shared library install names from `old_name` to `new_name`. # Changes all dependent shared library install names from `old_name` to `new_name`.
@ -168,25 +189,45 @@ module MachO
end end
# Write all (fat) data to the file used to initialize the instance. # Write all (fat) data to the file used to initialize the instance.
# @return [void]
# @raise [MachO::MachOError] if the instance was initialized without a file
# @note Overwrites all data in the file! # @note Overwrites all data in the file!
def write! def write!
if filename.nil?
raise MachOError.new("cannot write to a default file when initialized from a binary string")
else
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
end
private private
# Obtain the fat header from raw file data. # Obtain the fat header from raw file data.
# @return [MachO::FatHeader] the fat header # @return [MachO::FatHeader] the fat header
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic # @raise [MachO::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::MachOBinaryError] if the magic is for a non-fat Mach-O file
# @raise [MachO::JavaClassFileError] if the file is a Java classfile
# @private # @private
def get_fat_header def get_fat_header
magic, nfat_arch = @raw_data[0..7].unpack("N2") # the smallest fat Mach-O header is 8 bytes
raise TruncatedFileError.new if @raw_data.size < 8
raise MagicError.new(magic) unless MachO.magic?(magic) fh = FatHeader.new_from_bin(@raw_data[0, FatHeader.bytesize])
raise MachOBinaryError.new unless MachO.fat_magic?(magic)
FatHeader.new(magic, nfat_arch) raise MagicError.new(fh.magic) unless MachO.magic?(fh.magic)
raise MachOBinaryError.new unless MachO.fat_magic?(fh.magic)
# Rationale: Java classfiles have the same magic as big-endian fat
# Mach-Os. Classfiles encode their version at the same offset as
# `nfat_arch` and the lowest version number is 43, so we error out
# if a file claims to have over 30 internal architectures. It's
# technically possible for a fat Mach-O to have over 30 architectures,
# but this is extremely unlikely and in practice distinguishes the two
# formats.
raise JavaClassFileError.new if fh.nfat_arch > 30
fh
end end
# Obtain an array of fat architectures from raw file data. # Obtain an array of fat architectures from raw file data.
@ -195,9 +236,10 @@ module MachO
def get_fat_archs def get_fat_archs
archs = [] archs = []
fa_off = FatHeader.bytesize
fa_len = FatArch.bytesize
header.nfat_arch.times do |i| header.nfat_arch.times do |i|
fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5") archs << FatArch.new_from_bin(@raw_data[fa_off + (fa_len * i), fa_len])
archs << FatArch.new(*fields)
end end
archs archs

View File

@ -33,14 +33,11 @@ module MachO
# any CPU (unused?) # any CPU (unused?)
CPU_TYPE_ANY = -1 CPU_TYPE_ANY = -1
# x86 compatible CPUs
CPU_TYPE_X86 = 0x07
# i386 and later compatible CPUs # i386 and later compatible CPUs
CPU_TYPE_I386 = CPU_TYPE_X86 CPU_TYPE_I386 = 0x07
# x86_64 (AMD64) compatible CPUs # x86_64 (AMD64) compatible CPUs
CPU_TYPE_X86_64 = (CPU_TYPE_X86 | CPU_ARCH_ABI64) CPU_TYPE_X86_64 = (CPU_TYPE_I386 | CPU_ARCH_ABI64)
# PowerPC compatible CPUs (7400 series?) # PowerPC compatible CPUs (7400 series?)
CPU_TYPE_POWERPC = 0x12 CPU_TYPE_POWERPC = 0x12
@ -51,7 +48,6 @@ module MachO
# association of cpu types to string representations # association of cpu types to string representations
CPU_TYPES = { CPU_TYPES = {
CPU_TYPE_ANY => "CPU_TYPE_ANY", CPU_TYPE_ANY => "CPU_TYPE_ANY",
CPU_TYPE_X86 => "CPU_TYPE_X86",
CPU_TYPE_I386 => "CPU_TYPE_I386", CPU_TYPE_I386 => "CPU_TYPE_I386",
CPU_TYPE_X86_64 => "CPU_TYPE_X86_64", CPU_TYPE_X86_64 => "CPU_TYPE_X86_64",
CPU_TYPE_POWERPC => "CPU_TYPE_POWERPC", CPU_TYPE_POWERPC => "CPU_TYPE_POWERPC",
@ -166,7 +162,7 @@ module MachO
# @return [Fixnum] the number of fat architecture structures following the header # @return [Fixnum] the number of fat architecture structures following the header
attr_reader :nfat_arch attr_reader :nfat_arch
FORMAT = "VV" FORMAT = "N2" # always big-endian
SIZEOF = 8 SIZEOF = 8
# @api private # @api private
@ -195,7 +191,7 @@ module MachO
# @return [Fixnum] the alignment, as a power of 2 # @return [Fixnum] the alignment, as a power of 2
attr_reader :align attr_reader :align
FORMAT = "VVVVV" FORMAT = "N5" # always big-endian
SIZEOF = 20 SIZEOF = 20
# @api private # @api private
@ -239,7 +235,9 @@ module MachO
flags) flags)
@magic = magic @magic = magic
@cputype = cputype @cputype = cputype
@cpusubtype = cpusubtype # For now we're not interested in additional capability bits also to be
# found in the `cpusubtype` field. We only care about the CPU sub-type.
@cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
@filetype = filetype @filetype = filetype
@ncmds = ncmds @ncmds = ncmds
@sizeofcmds = sizeofcmds @sizeofcmds = sizeofcmds

View File

@ -830,7 +830,7 @@ module MachO
SIZEOF = 24 SIZEOF = 24
# @api private # @api private
def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid) def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
super(raw_data, offset, cmd, cmdsize) super(raw_data, offset, cmd, cmdsize)
@cryptoff = cryptoff @cryptoff = cryptoff
@cryptsize = cryptsize @cryptsize = cryptsize

View File

@ -215,7 +215,11 @@ module MachO
# All shared libraries linked to the Mach-O. # All shared libraries linked to the Mach-O.
# @return [Array<String>] an array of all shared libraries # @return [Array<String>] an array of all shared libraries
def linked_dylibs def linked_dylibs
dylib_load_commands.map(&:name).map(&:to_s) # Some linkers produce multiple `LC_LOAD_DYLIB` load commands for the same
# library, but at this point we're really only interested in a list of
# unique libraries this Mach-O file links to, thus: `uniq`. (This is also
# for consistency with `FatFile` that merges this list across all archs.)
dylib_load_commands.map(&:name).map(&:to_s).uniq
end end
# Changes the shared library `old_name` to `new_name` # Changes the shared library `old_name` to `new_name`
@ -293,7 +297,6 @@ module MachO
end end
# Write all Mach-O data to the file used to initialize the instance. # Write all Mach-O data to the file used to initialize the instance.
# @raise [MachOError] if the instance was created from a binary string
# @return [void] # @return [void]
# @raise [MachO::MachOError] if the instance was initialized without a file # @raise [MachO::MachOError] if the instance was initialized without a file
# @note Overwrites all data in the file! # @note Overwrites all data in the file!
@ -310,30 +313,29 @@ module MachO
# The file's Mach-O header structure. # The file's Mach-O header structure.
# @return [MachO::MachHeader] if the Mach-O is 32-bit # @return [MachO::MachHeader] if the Mach-O is 32-bit
# @return [MachO::MachHeader64] if the Mach-O is 64-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 # @private
def get_mach_header def get_mach_header
magic = get_magic # the smallest Mach-O header is 28 bytes
cputype = get_cputype raise TruncatedFileError.new if @raw_data.size < 28
cpusubtype = get_cpusubtype
filetype = get_filetype
ncmds = get_ncmds
sizeofcmds = get_sizeofcmds
flags = get_flags
if MachO.magic32?(magic) magic = get_and_check_magic
MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags) mh_klass = MachO.magic32?(magic) ? MachHeader : MachHeader64
else mh = mh_klass.new_from_bin(@raw_data[0, mh_klass.bytesize])
# the reserved field is...reserved, so just fill it with 0
MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0) check_cputype(mh.cputype)
end check_cpusubtype(mh.cpusubtype)
check_filetype(mh.filetype)
mh
end end
# The file's magic number. # Read just the file's magic number and check its validity.
# @return [Fixnum] the magic # @return [Fixnum] the magic
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic # @raise [MachO::MagicError] if the magic is not valid Mach-O magic
# @raise [MachO::FatBinaryError] if the magic is for a Fat file # @raise [MachO::FatBinaryError] if the magic is for a Fat file
# @private # @private
def get_magic def get_and_check_magic
magic = @raw_data[0..3].unpack("N").first magic = @raw_data[0..3].unpack("N").first
raise MagicError.new(magic) unless MachO.magic?(magic) raise MagicError.new(magic) unless MachO.magic?(magic)
@ -342,62 +344,29 @@ module MachO
magic magic
end end
# The file's CPU type. # Check the file's CPU type.
# @return [Fixnum] the CPU type # @param cputype [Fixnum] the CPU type
# @raise [MachO::CPUTypeError] if the CPU type is unknown # @raise [MachO::CPUTypeError] if the CPU type is unknown
# @private # @private
def get_cputype def check_cputype(cputype)
cputype = @raw_data[4..7].unpack("V").first
raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype) raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype)
cputype
end end
# The file's CPU subtype. # Check the file's CPU sub-type.
# @return [Fixnum] the CPU subtype # @param cpusubtype [Fixnum] the CPU subtype
# @raise [MachO::CPUSubtypeError] if the CPU subtype is unknown # @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown
# @private # @private
def get_cpusubtype def check_cpusubtype(cpusubtype)
cpusubtype = @raw_data[8..11].unpack("V").first # Only check sub-type w/o capability bits (see `get_mach_header`).
cpusubtype &= ~CPU_SUBTYPE_LIB64 # this mask isn't documented!
raise CPUSubtypeError.new(cpusubtype) unless CPU_SUBTYPES.key?(cpusubtype) raise CPUSubtypeError.new(cpusubtype) unless CPU_SUBTYPES.key?(cpusubtype)
cpusubtype
end end
# The file's type. # Check the file's type.
# @return [Fixnum] the file type # @param filetype [Fixnum] the file type
# @raise [MachO::FiletypeError] if the file type is unknown # @raise [MachO::FiletypeError] if the file type is unknown
# @private # @private
def get_filetype def check_filetype(filetype)
filetype = @raw_data[12..15].unpack("V").first
raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype) raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype)
filetype
end
# The number of load commands in the file.
# @return [Fixnum] the number of load commands
# @private
def get_ncmds
@raw_data[16..19].unpack("V").first
end
# The size of all load commands, in bytes.
# return [Fixnum] the size of all load commands
# @private
def get_sizeofcmds
@raw_data[20..23].unpack("V").first
end
# The Mach-O header's flags.
# @return [Fixnum] the flags
# @private
def get_flags
@raw_data[24..27].unpack("V").first
end end
# All load commands in the file. # All load commands in the file.
@ -484,19 +453,19 @@ module MachO
new_size = cmd.class.bytesize + new_str.size new_size = cmd.class.bytesize + new_str.size
new_sizeofcmds += new_size - cmd.cmdsize new_sizeofcmds += new_size - cmd.cmdsize
low_fileoff = 2**64 # ULLONGMAX low_fileoff = @raw_data.size
# calculate the low file offset (offset to first section data) # calculate the low file offset (offset to first section data)
segments.each do |seg| segments.each do |seg|
sections(seg).each do |sect| sections(seg).each do |sect|
if sect.size != 0 && !sect.flag?(:S_ZEROFILL) && next if sect.size == 0
!sect.flag?(:S_THREAD_LOCAL_ZEROFILL) && next if sect.flag?(:S_ZEROFILL)
sect.offset < low_fileoff next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
next unless sect.offset < low_fileoff
low_fileoff = sect.offset low_fileoff = sect.offset
end end
end end
end
if new_sizeofcmds + header.class.bytesize > low_fileoff if new_sizeofcmds + header.class.bytesize > low_fileoff
raise HeaderPadError.new(@filename) raise HeaderPadError.new(@filename)

View File

@ -41,7 +41,7 @@ module MachO
# @return [void] # @return [void]
# @todo unstub # @todo unstub
def self.change_rpath(filename, old_path, new_path) def self.change_rpath(filename, old_path, new_path)
raise "stub" raise UnimplementedError.new("changing rpaths in a Mach-O")
end end
# Add a runtime path to a Mach-O or Fat binary, overwriting the source file. # Add a runtime path to a Mach-O or Fat binary, overwriting the source file.
@ -50,7 +50,7 @@ module MachO
# @return [void] # @return [void]
# @todo unstub # @todo unstub
def self.add_rpath(filename, new_path) def self.add_rpath(filename, new_path)
raise "stub" raise UnimplementedError.new("adding rpaths to a Mach-O")
end end
# Delete a runtime path from a Mach-O or Fat binary, overwriting the source file. # Delete a runtime path from a Mach-O or Fat binary, overwriting the source file.
@ -59,7 +59,7 @@ module MachO
# @return [void] # @return [void]
# @todo unstub # @todo unstub
def self.delete_rpath(filename, old_path) def self.delete_rpath(filename, old_path)
raise "stub" raise UnimplementedError.new("removing rpaths from a Mach-O")
end end
end end
end end