Vendor ruby-macho-2.1.0

This commit is contained in:
Mike McQuaid 2018-10-02 16:40:10 +01:00
parent d9dbe26bda
commit 580aac1053
No known key found for this signature in database
GPG Key ID: 48A898132FD8EE70
12 changed files with 145 additions and 29 deletions

View File

@ -12,4 +12,4 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-5.2.1/l
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/backports-3.11.4/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/backports-3.11.4/lib"
$:.unshift "#{path}/" $:.unshift "#{path}/"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.4.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.4.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.0.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.1.0/lib"

View File

@ -12,7 +12,7 @@ require_relative "macho/tools"
# The primary namespace for ruby-macho. # The primary namespace for ruby-macho.
module MachO module MachO
# release version # release version
VERSION = "2.0.0".freeze VERSION = "2.1.0".freeze
# Opens the given filename as a MachOFile or FatFile, depending on its magic. # Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened # @param filename [String] the file being opened

View File

@ -194,4 +194,14 @@ module MachO
super "Unimplemented: #{thing}" super "Unimplemented: #{thing}"
end end
end end
# Raised when attempting to create a {FatFile} from one or more {MachOFile}s
# whose offsets will not fit within the resulting 32-bit {Headers::FatArch#offset} fields.
class FatArchOffsetOverflowError < MachOError
# @param offset [Integer] the offending offset
def initialize(offset)
super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset." \
" Consider merging with `fat64: true`"
end
end
end end

View File

@ -14,7 +14,7 @@ module MachO
# @return [Headers::FatHeader] the file's header # @return [Headers::FatHeader] the file's header
attr_reader :header attr_reader :header
# @return [Array<Headers::FatArch>] an array of fat architectures # @return [Array<Headers::FatArch>, Array<Headers::FatArch64] an array of fat architectures
attr_reader :fat_archs attr_reader :fat_archs
# @return [Array<MachOFile>] an array of Mach-O binaries # @return [Array<MachOFile>] an array of Mach-O binaries
@ -22,37 +22,49 @@ module MachO
# Creates a new FatFile from the given (single-arch) Mach-Os # Creates a new FatFile from the given (single-arch) Mach-Os
# @param machos [Array<MachOFile>] the machos to combine # @param machos [Array<MachOFile>] the machos to combine
# @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
# @return [FatFile] a new FatFile containing the give machos # @return [FatFile] a new FatFile containing the give machos
# @raise [ArgumentError] if less than one Mach-O is given # @raise [ArgumentError] if less than one Mach-O is given
def self.new_from_machos(*machos) # @raise [FatArchOffsetOverflowError] if the Mach-Os are too big to be represented
# in a 32-bit {Headers::FatArch} and `fat64` is `false`.
def self.new_from_machos(*machos, fat64: false)
raise ArgumentError, "expected at least one Mach-O" if machos.empty? raise ArgumentError, "expected at least one Mach-O" if machos.empty?
fa_klass, magic = if fat64
[Headers::FatArch64, Headers::FAT_MAGIC_64]
else
[Headers::FatArch, Headers::FAT_MAGIC]
end
# put the smaller alignments further forwards in fat macho, so that we do less padding # put the smaller alignments further forwards in fat macho, so that we do less padding
machos = machos.sort_by(&:segment_alignment) machos = machos.sort_by(&:segment_alignment)
bin = +"" bin = +""
bin << Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size).serialize bin << Headers::FatHeader.new(magic, machos.size).serialize
offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize) offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)
macho_pads = {} macho_pads = {}
macho_bins = {}
machos.each do |macho| machos.each do |macho|
macho_offset = Utils.round(offset, 2**macho.segment_alignment) macho_offset = Utils.round(offset, 2**macho.segment_alignment)
if !fat64 && macho_offset > (2**32 - 1)
raise FatArchOffsetOverflowError, macho_offset
end
macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment) macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
macho_bins[macho] = macho.serialize
bin << Headers::FatArch.new(macho.header.cputype, macho.header.cpusubtype, bin << fa_klass.new(macho.header.cputype, macho.header.cpusubtype,
macho_offset, macho_bins[macho].bytesize, macho_offset, macho.serialize.bytesize,
macho.segment_alignment).serialize macho.segment_alignment).serialize
offset += (macho_bins[macho].bytesize + macho_pads[macho]) offset += (macho.serialize.bytesize + macho_pads[macho])
end end
machos.each do |macho| machos.each do |macho|
bin << Utils.nullpad(macho_pads[macho]) bin << Utils.nullpad(macho_pads[macho])
bin << macho_bins[macho] bin << macho.serialize
end end
new_from_bin(bin) new_from_bin(bin)
@ -278,6 +290,7 @@ module MachO
# @note Overwrites all data in the file! # @note Overwrites all data in the file!
def write! def write!
raise MachOError, "no initial file to write to" if filename.nil? raise MachOError, "no initial file to write to" if filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
@ -327,10 +340,12 @@ module MachO
def populate_fat_archs def populate_fat_archs
archs = [] archs = []
fa_off = Headers::FatHeader.bytesize fa_klass = Utils.fat_magic32?(header.magic) ? Headers::FatArch : Headers::FatArch64
fa_len = Headers::FatArch.bytesize fa_off = Headers::FatHeader.bytesize
fa_len = fa_klass.bytesize
header.nfat_arch.times do |i| header.nfat_arch.times do |i|
archs << Headers::FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len]) archs << fa_klass.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
end end
archs archs
@ -380,6 +395,7 @@ module MachO
# Strict mode: Immediately re-raise. Otherwise: Retain, check later. # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
raise error if strict raise error if strict
errors << error errors << error
end end
end end

View File

@ -6,11 +6,19 @@ module MachO
FAT_MAGIC = 0xcafebabe FAT_MAGIC = 0xcafebabe
# little-endian fat magic # little-endian fat magic
# this is defined, but should never appear in ruby-macho code because # @note This is defined for completeness, but should never appear in ruby-macho code,
# fat headers are always big-endian and therefore always unpacked as such. # since fat headers are always big-endian.
# @api private # @api private
FAT_CIGAM = 0xbebafeca FAT_CIGAM = 0xbebafeca
# 64-bit big-endian fat magic
FAT_MAGIC_64 = 0xcafebabf
# 64-bit little-endian fat magic
# @note This is defined for completeness, but should never appear in ruby-macho code,
# since fat headers are always big-endian.
FAT_CIGAM_64 = 0xbfbafeca
# 32-bit big-endian magic # 32-bit big-endian magic
# @api private # @api private
MH_MAGIC = 0xfeedface MH_MAGIC = 0xfeedface
@ -31,6 +39,7 @@ module MachO
# @api private # @api private
MH_MAGICS = { MH_MAGICS = {
FAT_MAGIC => "FAT_MAGIC", FAT_MAGIC => "FAT_MAGIC",
FAT_MAGIC_64 => "FAT_MAGIC_64",
MH_MAGIC => "MH_MAGIC", MH_MAGIC => "MH_MAGIC",
MH_CIGAM => "MH_CIGAM", MH_CIGAM => "MH_CIGAM",
MH_MAGIC_64 => "MH_MAGIC_64", MH_MAGIC_64 => "MH_MAGIC_64",
@ -41,6 +50,11 @@ module MachO
# @api private # @api private
CPU_ARCH_ABI64 = 0x01000000 CPU_ARCH_ABI64 = 0x01000000
# mask for CPUs with 64-bit architectures (when running a 32-bit ABI?)
# @see https://github.com/Homebrew/ruby-macho/issues/113
# @api private
CPU_ARCH_ABI32 = 0x02000000
# any CPU (unused?) # any CPU (unused?)
# @api private # @api private
CPU_TYPE_ANY = -1 CPU_TYPE_ANY = -1
@ -69,6 +83,10 @@ module MachO
# @api private # @api private
CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64) CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64)
# 64-bit ARM compatible CPUs (running in 32-bit mode?)
# @see https://github.com/Homebrew/ruby-macho/issues/113
CPU_TYPE_ARM64_32 = (CPU_TYPE_ARM | CPU_ARCH_ABI32)
# PowerPC compatible CPUs # PowerPC compatible CPUs
# @api private # @api private
CPU_TYPE_POWERPC = 0x12 CPU_TYPE_POWERPC = 0x12
@ -85,6 +103,7 @@ module MachO
CPU_TYPE_X86_64 => :x86_64, CPU_TYPE_X86_64 => :x86_64,
CPU_TYPE_ARM => :arm, CPU_TYPE_ARM => :arm,
CPU_TYPE_ARM64 => :arm64, CPU_TYPE_ARM64 => :arm64,
CPU_TYPE_ARM64_32 => :arm64_32,
CPU_TYPE_POWERPC => :ppc, CPU_TYPE_POWERPC => :ppc,
CPU_TYPE_POWERPC64 => :ppc64, CPU_TYPE_POWERPC64 => :ppc64,
}.freeze }.freeze
@ -218,6 +237,10 @@ module MachO
# @api private # @api private
CPU_SUBTYPE_ARM64_V8 = 1 CPU_SUBTYPE_ARM64_V8 = 1
# the v8 sub-type for `CPU_TYPE_ARM64_32`
# @api private
CPU_SUBTYPE_ARM64_32_V8 = 1
# the lowest common sub-type for `CPU_TYPE_MC88000` # the lowest common sub-type for `CPU_TYPE_MC88000`
# @api private # @api private
CPU_SUBTYPE_MC88000_ALL = 0 CPU_SUBTYPE_MC88000_ALL = 0
@ -328,6 +351,9 @@ module MachO
CPU_SUBTYPE_ARM64_ALL => :arm64, CPU_SUBTYPE_ARM64_ALL => :arm64,
CPU_SUBTYPE_ARM64_V8 => :arm64v8, CPU_SUBTYPE_ARM64_V8 => :arm64v8,
}.freeze, }.freeze,
CPU_TYPE_ARM64_32 => {
CPU_SUBTYPE_ARM64_32_V8 => :arm64_32v8,
}.freeze,
CPU_TYPE_POWERPC => { CPU_TYPE_POWERPC => {
CPU_SUBTYPE_POWERPC_ALL => :ppc, CPU_SUBTYPE_POWERPC_ALL => :ppc,
CPU_SUBTYPE_POWERPC_601 => :ppc601, CPU_SUBTYPE_POWERPC_601 => :ppc601,
@ -486,8 +512,10 @@ module MachO
end end
end end
# Fat binary header architecture structure. A Fat binary has one or more of # 32-bit fat binary header architecture structure. A 32-bit fat Mach-O has one or more of
# these, representing one or more internal Mach-O blobs. # these, indicating one or more internal Mach-O blobs.
# @note "32-bit" indicates the fact that this structure stores 32-bit offsets, not that the
# Mach-Os that it points to necessarily *are* 32-bit.
# @see MachO::Headers::FatHeader # @see MachO::Headers::FatHeader
class FatArch < MachOStructure class FatArch < MachOStructure
# @return [Integer] the CPU type of the Mach-O # @return [Integer] the CPU type of the Mach-O
@ -505,10 +533,10 @@ module MachO
# @return [Integer] the alignment, as a power of 2 # @return [Integer] the alignment, as a power of 2
attr_reader :align attr_reader :align
# always big-endian # @note Always big endian.
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
FORMAT = "N5".freeze FORMAT = "L>5".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
# @api private # @api private
@ -542,6 +570,43 @@ module MachO
end end
end end
# 64-bit fat binary header architecture structure. A 64-bit fat Mach-O has one or more of
# these, indicating one or more internal Mach-O blobs.
# @note "64-bit" indicates the fact that this structure stores 64-bit offsets, not that the
# Mach-Os that it points to necessarily *are* 64-bit.
# @see MachO::Headers::FatHeader
class FatArch64 < FatArch
# @return [void]
attr_reader :reserved
# @note Always big endian.
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>2Q>2L>2".freeze
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 32
# @api private
def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
super(cputype, cpusubtype, offset, size, align)
@reserved = reserved
end
# @return [String] the serialized fields of the fat arch
def serialize
[cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
end
# @return [Hash] a hash representation of this {FatArch64}
def to_h
{
"reserved" => reserved,
}.merge super
end
end
# 32-bit Mach-O file header structure # 32-bit Mach-O file header structure
class MachHeader < MachOStructure class MachHeader < MachOStructure
# @return [Integer] the magic number # @return [Integer] the magic number
@ -593,7 +658,9 @@ module MachO
# @return [Boolean] true if `flag` is present in the header's flag section # @return [Boolean] true if `flag` is present in the header's flag section
def flag?(flag) def flag?(flag)
flag = MH_FLAGS[flag] flag = MH_FLAGS[flag]
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end

View File

@ -242,6 +242,7 @@ module MachO
# @api private # @api private
def serialize(context) def serialize(context)
raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable? raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable?
format = Utils.specialize_format(FORMAT, context.endianness) format = Utils.specialize_format(FORMAT, context.endianness)
[cmd, SIZEOF].pack(format) [cmd, SIZEOF].pack(format)
end end
@ -298,7 +299,9 @@ module MachO
lc_end = view.offset + lc.cmdsize - 1 lc_end = view.offset + lc.cmdsize - 1
raw_string = view.raw_data.slice(lc_str_abs..lc_end) raw_string = view.raw_data.slice(lc_str_abs..lc_end)
@string, null_byte, _padding = raw_string.partition("\x00") @string, null_byte, _padding = raw_string.partition("\x00")
raise LCStrMalformedError, lc if null_byte.empty? raise LCStrMalformedError, lc if null_byte.empty?
@string_offset = lc_str @string_offset = lc_str
else else
@string = lc_str @string = lc_str
@ -473,7 +476,9 @@ module MachO
# @return [Boolean] true if `flag` is present in the segment's flag field # @return [Boolean] true if `flag` is present in the segment's flag field
def flag?(flag) def flag?(flag)
flag = SEGMENT_FLAGS[flag] flag = SEGMENT_FLAGS[flag]
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end

View File

@ -431,6 +431,7 @@ module MachO
# @note Overwrites all data in the file! # @note Overwrites all data in the file!
def write! def write!
raise MachOError, "no initial file to write to" if @filename.nil? raise MachOError, "no initial file to write to" if @filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end

View File

@ -150,7 +150,9 @@ module MachO
# @return [Boolean] whether the flag is present in the section's {flags} # @return [Boolean] whether the flag is present in the section's {flags}
def flag?(flag) def flag?(flag)
flag = SECTION_FLAGS[flag] flag = SECTION_FLAGS[flag]
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end

View File

@ -89,8 +89,9 @@ module MachO
# Merge multiple Mach-Os into one universal (Fat) binary. # Merge multiple Mach-Os into one universal (Fat) binary.
# @param filename [String] the fat binary to create # @param filename [String] the fat binary to create
# @param files [Array<String>] the files to merge # @param files [Array<String>] the files to merge
# @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
# @return [void] # @return [void]
def self.merge_machos(filename, *files) def self.merge_machos(filename, *files, fat64: false)
machos = files.map do |file| machos = files.map do |file|
macho = MachO.open(file) macho = MachO.open(file)
case macho case macho
@ -101,7 +102,7 @@ module MachO
end end
end.flatten end.flatten
fat_macho = MachO::FatFile.new_from_machos(*machos) fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
fat_macho.write(filename) fat_macho.write(filename)
end end
end end

View File

@ -75,35 +75,49 @@ module MachO
# @param num [Integer] the number being checked # @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid Fat magic number # @return [Boolean] whether `num` is a valid Fat magic number
def self.fat_magic?(num) def self.fat_magic?(num)
[Headers::FAT_MAGIC, Headers::FAT_MAGIC_64].include? num
end
# Compares the given number to valid 32-bit Fat magic numbers.
# @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid 32-bit fat magic number
def self.fat_magic32?(num)
num == Headers::FAT_MAGIC num == Headers::FAT_MAGIC
end end
# Compares the given number to valid 64-bit Fat magic numbers.
# @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid 64-bit fat magic number
def self.fat_magic64?(num)
num == Headers::FAT_MAGIC_64
end
# Compares the given number to valid 32-bit Mach-O magic numbers. # Compares the given number to valid 32-bit Mach-O magic numbers.
# @param num [Integer] the number being checked # @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid 32-bit magic number # @return [Boolean] whether `num` is a valid 32-bit magic number
def self.magic32?(num) def self.magic32?(num)
num == Headers::MH_MAGIC || num == Headers::MH_CIGAM [Headers::MH_MAGIC, Headers::MH_CIGAM].include? num
end end
# Compares the given number to valid 64-bit Mach-O magic numbers. # Compares the given number to valid 64-bit Mach-O magic numbers.
# @param num [Integer] the number being checked # @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid 64-bit magic number # @return [Boolean] whether `num` is a valid 64-bit magic number
def self.magic64?(num) def self.magic64?(num)
num == Headers::MH_MAGIC_64 || num == Headers::MH_CIGAM_64 [Headers::MH_MAGIC_64, Headers::MH_CIGAM_64].include? num
end end
# Compares the given number to valid little-endian magic numbers. # Compares the given number to valid little-endian magic numbers.
# @param num [Integer] the number being checked # @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid little-endian magic number # @return [Boolean] whether `num` is a valid little-endian magic number
def self.little_magic?(num) def self.little_magic?(num)
num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64 [Headers::MH_CIGAM, Headers::MH_CIGAM_64].include? num
end end
# Compares the given number to valid big-endian magic numbers. # Compares the given number to valid big-endian magic numbers.
# @param num [Integer] the number being checked # @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid big-endian magic number # @return [Boolean] whether `num` is a valid big-endian magic number
def self.big_magic?(num) def self.big_magic?(num)
num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64 [Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
end end
end end
end end