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.
* [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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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