vendor: Update ruby-macho to 1.1.0.

This commit is contained in:
William Woodruff 2017-03-26 01:23:14 -04:00
parent 422afa0b49
commit 024264c381
No known key found for this signature in database
GPG Key ID: 85AE00C504833B3C
12 changed files with 2358 additions and 2257 deletions

View File

@ -3,7 +3,7 @@ Vendored Dependencies
* [plist](https://github.com/bleything/plist), version 3.1.0 * [plist](https://github.com/bleything/plist), version 3.1.0
* [ruby-macho](https://github.com/Homebrew/ruby-macho), version 0.2.6 * [ruby-macho](https://github.com/Homebrew/ruby-macho), version 1.1.0
## Licenses: ## Licenses:
@ -33,7 +33,7 @@ Vendored Dependencies
### ruby-macho ### ruby-macho
> The MIT License > The MIT License
> Copyright (c) 2015, 2016 William Woodruff <william @ tuffbizz.com> > Copyright (c) 2015, 2016, 2017 William Woodruff <william @ tuffbizz.com>
> >
> Permission is hereby granted, free of charge, to any person obtaining a copy > Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal > of this software and associated documentation files (the "Software"), to deal

View File

@ -5,7 +5,6 @@ require "#{File.dirname(__FILE__)}/macho/load_commands"
require "#{File.dirname(__FILE__)}/macho/sections" require "#{File.dirname(__FILE__)}/macho/sections"
require "#{File.dirname(__FILE__)}/macho/macho_file" require "#{File.dirname(__FILE__)}/macho/macho_file"
require "#{File.dirname(__FILE__)}/macho/fat_file" require "#{File.dirname(__FILE__)}/macho/fat_file"
require "#{File.dirname(__FILE__)}/macho/open"
require "#{File.dirname(__FILE__)}/macho/exceptions" require "#{File.dirname(__FILE__)}/macho/exceptions"
require "#{File.dirname(__FILE__)}/macho/utils" require "#{File.dirname(__FILE__)}/macho/utils"
require "#{File.dirname(__FILE__)}/macho/tools" require "#{File.dirname(__FILE__)}/macho/tools"
@ -13,5 +12,29 @@ require "#{File.dirname(__FILE__)}/macho/tools"
# The primary namespace for ruby-macho. # The primary namespace for ruby-macho.
module MachO module MachO
# release version # release version
VERSION = "0.2.6".freeze VERSION = "1.1.0".freeze
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened
# @return [MachOFile] if the file is a Mach-O
# @return [FatFile] if the file is a Fat file
# @raise [ArgumentError] if the given file does not exist
# @raise [TruncatedFileError] if the file is too small to have a valid header
# @raise [MagicError] if the file's magic is not valid Mach-O magic
def self.open(filename)
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 Utils.fat_magic?(magic)
file = FatFile.new(filename)
elsif Utils.magic?(magic)
file = MachOFile.new(filename)
else
raise MagicError, magic
end
file
end
end end

View File

@ -80,7 +80,8 @@ module MachO
# @param cputype [Fixnum] the CPU type of the unknown pair # @param cputype [Fixnum] the CPU type of the unknown pair
# @param cpusubtype [Fixnum] the CPU sub-type of the unknown pair # @param cpusubtype [Fixnum] the CPU sub-type of the unknown pair
def initialize(cputype, cpusubtype) def initialize(cputype, cpusubtype)
super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype} (for CPU type: 0x#{"%08x" % cputype})" super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype}" \
" (for CPU type: 0x#{"%08x" % cputype})"
end end
end end
@ -108,13 +109,15 @@ module MachO
end end
end end
# Raised when the number of arguments used to create a load command manually is wrong. # Raised when the number of arguments used to create a load command manually
# is wrong.
class LoadCommandCreationArityError < MachOError class LoadCommandCreationArityError < MachOError
# @param cmd_sym [Symbol] the load command's symbol # @param cmd_sym [Symbol] the load command's symbol
# @param expected_arity [Fixnum] the number of arguments expected # @param expected_arity [Fixnum] the number of arguments expected
# @param actual_arity [Fixnum] the number of arguments received # @param actual_arity [Fixnum] the number of arguments received
def initialize(cmd_sym, expected_arity, actual_arity) def initialize(cmd_sym, expected_arity, actual_arity)
super "Expected #{expected_arity} arguments for #{cmd_sym} creation, got #{actual_arity}" super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
" got #{actual_arity}"
end end
end end
@ -130,7 +133,8 @@ module MachO
class LCStrMalformedError < MachOError class LCStrMalformedError < MachOError
# @param lc [MachO::LoadCommand] the load command containing the string # @param lc [MachO::LoadCommand] the load command containing the string
def initialize(lc) def initialize(lc)
super "Load command #{lc.type} at offset #{lc.view.offset} contains a malformed string" super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
" malformed string"
end end
end end

View File

@ -1,24 +1,50 @@
require "forwardable"
module MachO module MachO
# Represents a "Fat" file, which contains a header, a listing of available # Represents a "Fat" file, which contains a header, a listing of available
# architectures, and one or more Mach-O binaries. # architectures, and one or more Mach-O binaries.
# @see https://en.wikipedia.org/wiki/Mach-O#Multi-architecture_binaries # @see https://en.wikipedia.org/wiki/Mach-O#Multi-architecture_binaries
# @see MachO::MachOFile # @see MachOFile
class FatFile class FatFile
extend Forwardable
# @return [String] the filename loaded from, or nil if loaded from a binary string # @return [String] the filename loaded from, or nil if loaded from a binary string
attr_accessor :filename attr_accessor :filename
# @return [MachO::FatHeader] the file's header # @return [Headers::FatHeader] the file's header
attr_reader :header attr_reader :header
# @return [Array<MachO::FatArch>] an array of fat architectures # @return [Array<Headers::FatArch>] an array of fat architectures
attr_reader :fat_archs attr_reader :fat_archs
# @return [Array<MachO::MachOFile>] an array of Mach-O binaries # @return [Array<MachOFile>] an array of Mach-O binaries
attr_reader :machos attr_reader :machos
# Creates a new FatFile from the given (single-arch) Mach-Os
# @param machos [Array<MachOFile>] the machos to combine
# @return [FatFile] a new FatFile containing the give machos
def self.new_from_machos(*machos)
header = Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size)
offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
fat_archs = []
machos.each do |macho|
fat_archs << Headers::FatArch.new(macho.header.cputype,
macho.header.cpusubtype,
offset, macho.serialize.bytesize,
macho.alignment)
offset += macho.serialize.bytesize
end
bin = header.serialize
bin << fat_archs.map(&:serialize).join
bin << machos.map(&:serialize).join
new_from_bin(bin)
end
# Creates a new FatFile instance from a binary string. # Creates a new FatFile instance from a binary string.
# @param bin [String] a binary string containing raw Mach-O data # @param bin [String] a binary string containing raw Mach-O data
# @return [MachO::FatFile] a new FatFile # @return [FatFile] a new FatFile
def self.new_from_bin(bin) def self.new_from_bin(bin)
instance = allocate instance = allocate
instance.initialize_from_bin(bin) instance.initialize_from_bin(bin)
@ -38,7 +64,7 @@ module MachO
end end
# Initializes a new FatFile instance from a binary string. # Initializes a new FatFile instance from a binary string.
# @see MachO::FatFile.new_from_bin # @see new_from_bin
# @api private # @api private
def initialize_from_bin(bin) def initialize_from_bin(bin)
@filename = nil @filename = nil
@ -52,70 +78,41 @@ module MachO
@raw_data @raw_data
end end
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise # @!method object?
def object? # @return (see MachO::MachOFile#object?)
machos.first.object? # @!method executable?
end # @return (see MachO::MachOFile#executable?)
# @!method fvmlib?
# @return (see MachO::MachOFile#fvmlib?)
# @!method core?
# @return (see MachO::MachOFile#core?)
# @!method preload?
# @return (see MachO::MachOFile#preload?)
# @!method dylib?
# @return (see MachO::MachOFile#dylib?)
# @!method dylinker?
# @return (see MachO::MachOFile#dylinker?)
# @!method bundle?
# @return (see MachO::MachOFile#bundle?)
# @!method dsym?
# @return (see MachO::MachOFile#dsym?)
# @!method kext?
# @return (see MachO::MachOFile#kext?)
# @!method filetype
# @return (see MachO::MachOFile#filetype)
# @!method dylib_id
# @return (see MachO::MachOFile#dylib_id)
def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id
# @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise # @!method magic
def executable? # @return (see MachO::Headers::FatHeader#magic)
machos.first.executable? def_delegators :header, :magic
end
# @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
def fvmlib?
machos.first.fvmlib?
end
# @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
def core?
machos.first.core?
end
# @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
def preload?
machos.first.preload?
end
# @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
def dylib?
machos.first.dylib?
end
# @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
def dylinker?
machos.first.dylinker?
end
# @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
def bundle?
machos.first.bundle?
end
# @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
def dsym?
machos.first.dsym?
end
# @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
def kext?
machos.first.kext?
end
# @return [Fixnum] the file's magic number
def magic
header.magic
end
# @return [String] a string representation of the file's magic number # @return [String] a string representation of the file's magic number
def magic_string def magic_string
MH_MAGICS[magic] Headers::MH_MAGICS[magic]
end
# The file's type. Assumed to be the same for every Mach-O within.
# @return [Symbol] the filetype
def filetype
machos.first.filetype
end end
# Populate the instance's fields with the raw Fat Mach-O data. # Populate the instance's fields with the raw Fat Mach-O data.
@ -128,21 +125,13 @@ module MachO
end end
# All load commands responsible for loading dylibs in the file's Mach-O's. # All load commands responsible for loading dylibs in the file's Mach-O's.
# @return [Array<MachO::DylibCommand>] an array of DylibCommands # @return [Array<LoadCommands::DylibCommand>] an array of DylibCommands
def dylib_load_commands def dylib_load_commands
machos.map(&:dylib_load_commands).flatten machos.map(&:dylib_load_commands).flatten
end end
# The file's dylib ID. If the file is not a dylib, returns `nil`. # Changes the file's dylib ID to `new_id`. If the file is not a dylib,
# @example # does nothing.
# 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 # @example
# file.change_dylib_id('libFoo.dylib') # file.change_dylib_id('libFoo.dylib')
# @param new_id [String] the new dylib ID # @param new_id [String] the new dylib ID
@ -151,7 +140,7 @@ module MachO
# if false, fail only if all slices fail. # if false, fail only if all slices fail.
# @return [void] # @return [void]
# @raise [ArgumentError] if `new_id` is not a String # @raise [ArgumentError] if `new_id` is not a String
# @see MachO::MachOFile#linked_dylibs # @see MachOFile#linked_dylibs
def change_dylib_id(new_id, options = {}) def change_dylib_id(new_id, options = {})
raise ArgumentError, "argument must be a String" unless new_id.is_a?(String) raise ArgumentError, "argument must be a String" unless new_id.is_a?(String)
return unless machos.all?(&:dylib?) return unless machos.all?(&:dylib?)
@ -167,7 +156,7 @@ 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
# @see MachO::MachOFile#linked_dylibs # @see MachOFile#linked_dylibs
def linked_dylibs def linked_dylibs
# Individual architectures in a fat binary can link to different subsets # 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. # of libraries, but at this point we want to have the full picture, i.e.
@ -175,8 +164,9 @@ module MachO
machos.map(&:linked_dylibs).flatten.uniq 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
# In a fat file, this changes install names in all internal Mach-Os. # `new_name`. In a fat file, this changes install names in all internal
# Mach-Os.
# @example # @example
# file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib') # file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')
# @param old_name [String] the shared library name being changed # @param old_name [String] the shared library name being changed
@ -185,7 +175,7 @@ module MachO
# @option options [Boolean] :strict (true) if true, fail if one slice fails. # @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail. # if false, fail only if all slices fail.
# @return [void] # @return [void]
# @see MachO::MachOFile#change_install_name # @see MachOFile#change_install_name
def change_install_name(old_name, new_name, options = {}) def change_install_name(old_name, new_name, options = {})
each_macho(options) do |macho| each_macho(options) do |macho|
macho.change_install_name(old_name, new_name, options) macho.change_install_name(old_name, new_name, options)
@ -198,7 +188,7 @@ module MachO
# All runtime paths associated with the file's Mach-Os. # All runtime paths associated with the file's Mach-Os.
# @return [Array<String>] an array of all runtime paths # @return [Array<String>] an array of all runtime paths
# @see MachO::MachOFile#rpaths # @see MachOFile#rpaths
def rpaths def rpaths
# Can individual architectures have different runtime paths? # Can individual architectures have different runtime paths?
machos.map(&:rpaths).flatten.uniq machos.map(&:rpaths).flatten.uniq
@ -211,7 +201,7 @@ module MachO
# @option options [Boolean] :strict (true) if true, fail if one slice fails. # @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail. # if false, fail only if all slices fail.
# @return [void] # @return [void]
# @see MachO::MachOFile#change_rpath # @see MachOFile#change_rpath
def change_rpath(old_path, new_path, options = {}) def change_rpath(old_path, new_path, options = {})
each_macho(options) do |macho| each_macho(options) do |macho|
macho.change_rpath(old_path, new_path, options) macho.change_rpath(old_path, new_path, options)
@ -226,7 +216,7 @@ module MachO
# @option options [Boolean] :strict (true) if true, fail if one slice fails. # @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail. # if false, fail only if all slices fail.
# @return [void] # @return [void]
# @see MachO::MachOFile#add_rpath # @see MachOFile#add_rpath
def add_rpath(path, options = {}) def add_rpath(path, options = {})
each_macho(options) do |macho| each_macho(options) do |macho|
macho.add_rpath(path, options) macho.add_rpath(path, options)
@ -241,7 +231,7 @@ module MachO
# @option options [Boolean] :strict (true) if true, fail if one slice fails. # @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail. # if false, fail only if all slices fail.
# @return void # @return void
# @see MachO::MachOFile#delete_rpath # @see MachOFile#delete_rpath
def delete_rpath(path, options = {}) def delete_rpath(path, options = {})
each_macho(options) do |macho| each_macho(options) do |macho|
macho.delete_rpath(path, options) macho.delete_rpath(path, options)
@ -254,20 +244,21 @@ module MachO
# @example # @example
# file.extract(:i386) # => MachO::MachOFile # file.extract(:i386) # => MachO::MachOFile
# @param cputype [Symbol] the CPU type of the Mach-O being extracted # @param cputype [Symbol] the CPU type of the Mach-O being extracted
# @return [MachO::MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type # @return [MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type
def extract(cputype) def extract(cputype)
machos.select { |macho| macho.cputype == cputype }.first machos.select { |macho| macho.cputype == cputype }.first
end end
# Write all (fat) data to the given filename. # Write all (fat) data to the given filename.
# @param filename [String] the file to write to # @param filename [String] the file to write to
# @return [void]
def write(filename) def write(filename)
File.open(filename, "wb") { |f| f.write(@raw_data) } File.open(filename, "wb") { |f| f.write(@raw_data) }
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] # @return [void]
# @raise [MachO::MachOError] if the instance was initialized without a file # @raise [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? if filename.nil?
@ -280,17 +271,18 @@ module MachO
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 [Headers::FatHeader] the fat header
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header # @raise [TruncatedFileError] if the file is too small to have a
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic # valid header
# @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file # @raise [MagicError] if the magic is not valid Mach-O magic
# @raise [MachO::JavaClassFileError] if the file is a Java classfile # @raise [MachOBinaryError] if the magic is for a non-fat Mach-O file
# @raise [JavaClassFileError] if the file is a Java classfile
# @api private # @api private
def populate_fat_header def populate_fat_header
# the smallest fat Mach-O header is 8 bytes # the smallest fat Mach-O header is 8 bytes
raise TruncatedFileError if @raw_data.size < 8 raise TruncatedFileError if @raw_data.size < 8
fh = FatHeader.new_from_bin(:big, @raw_data[0, FatHeader.bytesize]) fh = Headers::FatHeader.new_from_bin(:big, @raw_data[0, Headers::FatHeader.bytesize])
raise MagicError, fh.magic unless Utils.magic?(fh.magic) raise MagicError, fh.magic unless Utils.magic?(fh.magic)
raise MachOBinaryError unless Utils.fat_magic?(fh.magic) raise MachOBinaryError unless Utils.fat_magic?(fh.magic)
@ -308,22 +300,22 @@ module MachO
end end
# Obtain an array of fat architectures from raw file data. # Obtain an array of fat architectures from raw file data.
# @return [Array<MachO::FatArch>] an array of fat architectures # @return [Array<Headers::FatArch>] an array of fat architectures
# @api private # @api private
def populate_fat_archs def populate_fat_archs
archs = [] archs = []
fa_off = FatHeader.bytesize fa_off = Headers::FatHeader.bytesize
fa_len = FatArch.bytesize fa_len = Headers::FatArch.bytesize
header.nfat_arch.times do |i| header.nfat_arch.times do |i|
archs << FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len]) archs << Headers::FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
end end
archs archs
end end
# Obtain an array of Mach-O blobs from raw file data. # Obtain an array of Mach-O blobs from raw file data.
# @return [Array<MachO::MachOFile>] an array of Mach-Os # @return [Array<MachOFile>] an array of Mach-Os
# @api private # @api private
def populate_machos def populate_machos
machos = [] machos = []
@ -351,7 +343,7 @@ module MachO
# @option options [Boolean] :strict (true) whether or not to fail loudly # @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, # with an exception if at least one Mach-O raises an exception. If false,
# only raises an exception if *all* Mach-Os raise exceptions. # only raises an exception if *all* Mach-Os raise exceptions.
# @raise [MachO::RecoverableModificationError] under the conditions of # @raise [RecoverableModificationError] under the conditions of
# the `:strict` option above. # the `:strict` option above.
# @api private # @api private
def each_macho(options = {}) def each_macho(options = {})
@ -373,5 +365,13 @@ module MachO
# Non-strict mode: Raise first error if *all* Mach-O slices failed. # Non-strict mode: Raise first error if *all* Mach-O slices failed.
raise errors.first if errors.size == machos.size raise errors.first if errors.size == machos.size
end end
# Return a single-arch Mach-O that represents this fat Mach-O for purposes
# of delegation.
# @return [MachOFile] the Mach-O file
# @api private
def canonical_macho
machos.first
end
end end
end end

View File

@ -1,4 +1,6 @@
module MachO module MachO
# Classes and constants for parsing the headers of Mach-O binaries.
module Headers
# big-endian fat magic # big-endian fat magic
# @api private # @api private
FAT_MAGIC = 0xcafebabe FAT_MAGIC = 0xcafebabe
@ -468,11 +470,16 @@ module MachO
@magic = magic @magic = magic
@nfat_arch = nfat_arch @nfat_arch = nfat_arch
end end
# @return [String] the serialized fields of the fat header
def serialize
[magic, nfat_arch].pack(FORMAT)
end
end end
# Fat binary header architecture structure. A Fat binary has one or more of # Fat binary header architecture structure. A Fat binary has one or more of
# these, representing one or more internal Mach-O blobs. # these, representing one or more internal Mach-O blobs.
# @see MachO::FatHeader # @see MachO::Headers::FatHeader
class FatArch < MachOStructure class FatArch < MachOStructure
# @return [Fixnum] the CPU type of the Mach-O # @return [Fixnum] the CPU type of the Mach-O
attr_reader :cputype attr_reader :cputype
@ -506,6 +513,11 @@ module MachO
@size = size @size = size
@align = align @align = align
end end
# @return [String] the serialized fields of the fat arch
def serialize
[cputype, cpusubtype, offset, size, align].pack(FORMAT)
end
end end
# 32-bit Mach-O file header structure # 32-bit Mach-O file header structure
@ -562,6 +574,71 @@ module MachO
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end
# @return [Boolean] whether or not the file is of type `MH_OBJECT`
def object?
filetype == Headers::MH_OBJECT
end
# @return [Boolean] whether or not the file is of type `MH_EXECUTE`
def executable?
filetype == Headers::MH_EXECUTE
end
# @return [Boolean] whether or not the file is of type `MH_FVMLIB`
def fvmlib?
filetype == Headers::MH_FVMLIB
end
# @return [Boolean] whether or not the file is of type `MH_CORE`
def core?
filetype == Headers::MH_CORE
end
# @return [Boolean] whether or not the file is of type `MH_PRELOAD`
def preload?
filetype == Headers::MH_PRELOAD
end
# @return [Boolean] whether or not the file is of type `MH_DYLIB`
def dylib?
filetype == Headers::MH_DYLIB
end
# @return [Boolean] whether or not the file is of type `MH_DYLINKER`
def dylinker?
filetype == Headers::MH_DYLINKER
end
# @return [Boolean] whether or not the file is of type `MH_BUNDLE`
def bundle?
filetype == Headers::MH_BUNDLE
end
# @return [Boolean] whether or not the file is of type `MH_DSYM`
def dsym?
filetype == Headers::MH_DSYM
end
# @return [Boolean] whether or not the file is of type `MH_KEXT_BUNDLE`
def kext?
filetype == Headers::MH_KEXT_BUNDLE
end
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
def magic32?
Utils.magic32?(magic)
end
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
def magic64?
Utils.magic64?(magic)
end
# @return [Fixnum] the file's internal alignment
def alignment
magic32? ? 4 : 8
end
end end
# 64-bit Mach-O file header structure # 64-bit Mach-O file header structure
@ -585,3 +662,4 @@ module MachO
end end
end end
end end
end

View File

@ -1,6 +1,8 @@
module MachO module MachO
# Classes and constants for parsing load commands in Mach-O binaries.
module LoadCommands
# load commands added after OS X 10.1 need to be bitwise ORed with # 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) # LC_REQ_DYLD to be recognized by the dynamic linker (dyld)
# @api private # @api private
LC_REQ_DYLD = 0x80000000 LC_REQ_DYLD = 0x80000000
@ -85,14 +87,21 @@ module MachO
LC_STRUCTURES = { LC_STRUCTURES = {
:LC_SEGMENT => "SegmentCommand", :LC_SEGMENT => "SegmentCommand",
:LC_SYMTAB => "SymtabCommand", :LC_SYMTAB => "SymtabCommand",
:LC_SYMSEG => "SymsegCommand", # obsolete # "obsolete"
:LC_THREAD => "ThreadCommand", # seems obsolete, but not documented as such :LC_SYMSEG => "SymsegCommand",
# seems obsolete, but not documented as such
:LC_THREAD => "ThreadCommand",
:LC_UNIXTHREAD => "ThreadCommand", :LC_UNIXTHREAD => "ThreadCommand",
:LC_LOADFVMLIB => "FvmlibCommand", # obsolete # "obsolete"
:LC_IDFVMLIB => "FvmlibCommand", # obsolete :LC_LOADFVMLIB => "FvmlibCommand",
:LC_IDENT => "IdentCommand", # obsolete # "obsolete"
:LC_FVMFILE => "FvmfileCommand", # reserved for internal use only :LC_IDFVMLIB => "FvmlibCommand",
:LC_PREPAGE => "LoadCommand", # reserved for internal use only, no public struct # "obsolete"
:LC_IDENT => "IdentCommand",
# "reserved for internal use only"
:LC_FVMFILE => "FvmfileCommand",
# "reserved for internal use only", no public struct
:LC_PREPAGE => "LoadCommand",
:LC_DYSYMTAB => "DysymtabCommand", :LC_DYSYMTAB => "DysymtabCommand",
:LC_LOAD_DYLIB => "DylibCommand", :LC_LOAD_DYLIB => "DylibCommand",
:LC_ID_DYLIB => "DylibCommand", :LC_ID_DYLIB => "DylibCommand",
@ -180,7 +189,7 @@ module MachO
# Instantiates a new LoadCommand given a view into its origin Mach-O # Instantiates a new LoadCommand given a view into its origin Mach-O
# @param view [MachO::MachOView] the load command's raw view # @param view [MachO::MachOView] the load command's raw view
# @return [MachO::LoadCommand] the new load command # @return [LoadCommand] the new load command
# @api private # @api private
def self.new_from_bin(view) def self.new_from_bin(view)
bin = view.raw_data.slice(view.offset, bytesize) bin = view.raw_data.slice(view.offset, bytesize)
@ -195,7 +204,7 @@ module MachO
def self.create(cmd_sym, *args) def self.create(cmd_sym, *args)
raise LoadCommandNotCreatableError, cmd_sym unless CREATABLE_LOAD_COMMANDS.include?(cmd_sym) raise LoadCommandNotCreatableError, cmd_sym unless CREATABLE_LOAD_COMMANDS.include?(cmd_sym)
klass = MachO.const_get LC_STRUCTURES[cmd_sym] klass = LoadCommands.const_get LC_STRUCTURES[cmd_sym]
cmd = LOAD_COMMAND_CONSTANTS[cmd_sym] cmd = LOAD_COMMAND_CONSTANTS[cmd_sym]
# cmd will be filled in, view and cmdsize will be left unpopulated # cmd will be filled in, view and cmdsize will be left unpopulated
@ -216,12 +225,12 @@ module MachO
@cmdsize = cmdsize @cmdsize = cmdsize
end end
# @return [Boolean] true if the load command can be serialized, false otherwise # @return [Boolean] whether the load command can be serialized
def serializable? def serializable?
CREATABLE_LOAD_COMMANDS.include?(LOAD_COMMANDS[cmd]) CREATABLE_LOAD_COMMANDS.include?(LOAD_COMMANDS[cmd])
end end
# @param context [MachO::LoadCommand::SerializationContext] the context # @param context [SerializationContext] the context
# to serialize into # to serialize into
# @return [String, nil] the serialized fields of the load command, or nil # @return [String, nil] the serialized fields of the load command, or nil
# if the load command can't be serialized # if the load command can't be serialized
@ -238,14 +247,16 @@ module MachO
view.offset view.offset
end end
# @return [Symbol] a symbol representation of the load command's identifying number # @return [Symbol] a symbol representation of the load command's
# identifying number
def type def type
LOAD_COMMANDS[cmd] LOAD_COMMANDS[cmd]
end end
alias to_sym type alias to_sym type
# @return [String] a string representation of the load command's identifying number # @return [String] a string representation of the load command's
# identifying number
def to_s def to_s
type.to_s type.to_s
end end
@ -255,9 +266,9 @@ module MachO
# pretend that strings stored in LCs are immediately available without # pretend that strings stored in LCs are immediately available without
# explicit operations on the raw Mach-O data. # explicit operations on the raw Mach-O data.
class LCStr class LCStr
# @param lc [MachO::LoadCommand] the load command # @param lc [LoadCommand] the load command
# @param lc_str [Fixnum, String] the offset to the beginning of the string, # @param lc_str [Fixnum, String] the offset to the beginning of the
# or the string itself if not being initialized with a view. # string, or the string itself if not being initialized with a view.
# @raise [MachO::LCStrMalformedError] if the string is malformed # @raise [MachO::LCStrMalformedError] if the string is malformed
# @todo devise a solution such that the `lc_str` parameter is not # @todo devise a solution such that the `lc_str` parameter is not
# interpreted differently depending on `lc.view`. The current behavior # interpreted differently depending on `lc.view`. The current behavior
@ -284,7 +295,8 @@ module MachO
@string @string
end end
# @return [Fixnum] the offset to the beginning of the string in the load command # @return [Fixnum] the offset to the beginning of the string in the
# load command
def to_i def to_i
@string_offset @string_offset
end end
@ -296,11 +308,13 @@ module MachO
# @return [Symbol] the endianness of the serialized load command # @return [Symbol] the endianness of the serialized load command
attr_reader :endianness attr_reader :endianness
# @return [Fixnum] the constant alignment value used to pad the serialized load command # @return [Fixnum] the constant alignment value used to pad the
# serialized load command
attr_reader :alignment attr_reader :alignment
# @param macho [MachO::MachOFile] the file to contextualize # @param macho [MachO::MachOFile] the file to contextualize
# @return [MachO::LoadCommand::SerializationContext] the resulting context # @return [SerializationContext] the
# resulting context
def self.context_for(macho) def self.context_for(macho)
new(macho.endianness, macho.alignment) new(macho.endianness, macho.alignment)
end end
@ -315,8 +329,9 @@ module MachO
end end
end end
# A load command containing a single 128-bit unique random number identifying # A load command containing a single 128-bit unique random number
# an object produced by static link editor. Corresponds to LC_UUID. # identifying an object produced by static link editor. Corresponds to
# LC_UUID.
class UUIDCommand < LoadCommand class UUIDCommand < LoadCommand
# @return [Array<Fixnum>] the UUID # @return [Array<Fixnum>] the UUID
attr_reader :uuid attr_reader :uuid
@ -401,17 +416,20 @@ module MachO
end end
# All sections referenced within this segment. # All sections referenced within this segment.
# @return [Array<MachO::Section>] if the Mach-O is 32-bit # @return [Array<MachO::Sections::Section>] if the Mach-O is 32-bit
# @return [Array<MachO::Section64>] if the Mach-O is 64-bit # @return [Array<MachO::Sections::Section64>] if the Mach-O is 64-bit
def sections def sections
klass = case self klass = case self
when MachO::SegmentCommand64 when SegmentCommand64
MachO::Section64 MachO::Sections::Section64
when MachO::SegmentCommand when SegmentCommand
MachO::Section MachO::Sections::Section
end end
bins = view.raw_data[view.offset + self.class.bytesize, nsects * klass.bytesize] offset = view.offset + self.class.bytesize
length = nsects * klass.bytesize
bins = view.raw_data[offset, length]
bins.unpack("a#{klass.bytesize}" * nsects).map do |bin| bins.unpack("a#{klass.bytesize}" * nsects).map do |bin|
klass.new_from_bin(view.endianness, bin) klass.new_from_bin(view.endianness, bin)
end end
@ -441,10 +459,11 @@ module MachO
end end
# A load command representing some aspect of shared libraries, depending # A load command representing some aspect of shared libraries, depending
# on filetype. Corresponds to LC_ID_DYLIB, LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, # on filetype. Corresponds to LC_ID_DYLIB, LC_LOAD_DYLIB,
# and LC_REEXPORT_DYLIB. # LC_LOAD_WEAK_DYLIB, and LC_REEXPORT_DYLIB.
class DylibCommand < LoadCommand class DylibCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the library's path name as an LCStr # @return [LCStr] the library's path
# name as an LCStr
attr_reader :name attr_reader :name
# @return [Fixnum] the library's build time stamp # @return [Fixnum] the library's build time stamp
@ -465,7 +484,8 @@ module MachO
SIZEOF = 24 SIZEOF = 24
# @api private # @api private
def initialize(view, cmd, cmdsize, name, timestamp, current_version, compatibility_version) def initialize(view, cmd, cmdsize, name, timestamp, current_version,
compatibility_version)
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@name = LCStr.new(self, name) @name = LCStr.new(self, name)
@timestamp = timestamp @timestamp = timestamp
@ -473,12 +493,15 @@ module MachO
@compatibility_version = compatibility_version @compatibility_version = compatibility_version
end end
# @param context [MachO::LoadCcommand::SerializationContext] the context # @param context [SerializationContext]
# the context
# @return [String] the serialized fields of the load command # @return [String] the serialized fields of the load command
# @api private # @api private
def serialize(context) def serialize(context)
format = Utils.specialize_format(FORMAT, context.endianness) format = Utils.specialize_format(FORMAT, context.endianness)
string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s) string_payload, string_offsets = Utils.pack_strings(SIZEOF,
context.alignment,
:name => name.to_s)
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:name], timestamp, current_version, [cmd, cmdsize, string_offsets[:name], timestamp, current_version,
compatibility_version].pack(format) + string_payload compatibility_version].pack(format) + string_payload
@ -489,7 +512,8 @@ module MachO
# on filetype. Corresponds to LC_ID_DYLINKER, LC_LOAD_DYLINKER, and # on filetype. Corresponds to LC_ID_DYLINKER, LC_LOAD_DYLINKER, and
# LC_DYLD_ENVIRONMENT. # LC_DYLD_ENVIRONMENT.
class DylinkerCommand < LoadCommand class DylinkerCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the dynamic linker's path name as an LCStr # @return [LCStr] the dynamic linker's
# path name as an LCStr
attr_reader :name attr_reader :name
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -506,12 +530,15 @@ module MachO
@name = LCStr.new(self, name) @name = LCStr.new(self, name)
end end
# @param context [MachO::LoadCcommand::SerializationContext] the context # @param context [SerializationContext]
# the context
# @return [String] the serialized fields of the load command # @return [String] the serialized fields of the load command
# @api private # @api private
def serialize(context) def serialize(context)
format = Utils.specialize_format(FORMAT, context.endianness) format = Utils.specialize_format(FORMAT, context.endianness)
string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s) string_payload, string_offsets = Utils.pack_strings(SIZEOF,
context.alignment,
:name => name.to_s)
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload
end end
@ -520,7 +547,8 @@ module MachO
# A load command used to indicate dynamic libraries used in prebinding. # A load command used to indicate dynamic libraries used in prebinding.
# Corresponds to LC_PREBOUND_DYLIB. # Corresponds to LC_PREBOUND_DYLIB.
class PreboundDylibCommand < LoadCommand class PreboundDylibCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the library's path name as an LCStr # @return [LCStr] the library's path
# name as an LCStr
attr_reader :name attr_reader :name
# @return [Fixnum] the number of modules in the library # @return [Fixnum] the number of modules in the library
@ -547,7 +575,8 @@ module MachO
end end
# A load command used to represent threads. # A load command used to represent threads.
# @note cctools-870 has all fields of thread_command commented out except common ones (cmd, cmdsize) # @note cctools-870 and onwards have all fields of thread_command commented
# out except the common ones (cmd, cmdsize)
class ThreadCommand < LoadCommand class ThreadCommand < LoadCommand
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
@ -565,7 +594,8 @@ module MachO
# @return [Fixnum] the address of the initialization routine # @return [Fixnum] the address of the initialization routine
attr_reader :init_address attr_reader :init_address
# @return [Fixnum] the index into the module table that the init routine is defined in # @return [Fixnum] the index into the module table that the init routine
# is defined in
attr_reader :init_module attr_reader :init_module
# @return [void] # @return [void]
@ -625,7 +655,7 @@ module MachO
# A load command signifying membership of a subframework containing the name # A load command signifying membership of a subframework containing the name
# of an umbrella framework. Corresponds to LC_SUB_FRAMEWORK. # of an umbrella framework. Corresponds to LC_SUB_FRAMEWORK.
class SubFrameworkCommand < LoadCommand class SubFrameworkCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the umbrella framework name as an LCStr # @return [LCStr] the umbrella framework name as an LCStr
attr_reader :umbrella attr_reader :umbrella
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -646,7 +676,7 @@ module MachO
# A load command signifying membership of a subumbrella containing the name # A load command signifying membership of a subumbrella containing the name
# of an umbrella framework. Corresponds to LC_SUB_UMBRELLA. # of an umbrella framework. Corresponds to LC_SUB_UMBRELLA.
class SubUmbrellaCommand < LoadCommand class SubUmbrellaCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the subumbrella framework name as an LCStr # @return [LCStr] the subumbrella framework name as an LCStr
attr_reader :sub_umbrella attr_reader :sub_umbrella
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -667,7 +697,7 @@ module MachO
# A load command signifying a sublibrary of a shared library. Corresponds # A load command signifying a sublibrary of a shared library. Corresponds
# to LC_SUB_LIBRARY. # to LC_SUB_LIBRARY.
class SubLibraryCommand < LoadCommand class SubLibraryCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the sublibrary name as an LCStr # @return [LCStr] the sublibrary name as an LCStr
attr_reader :sub_library attr_reader :sub_library
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -688,7 +718,7 @@ module MachO
# A load command signifying a shared library that is a subframework of # A load command signifying a shared library that is a subframework of
# an umbrella framework. Corresponds to LC_SUB_CLIENT. # an umbrella framework. Corresponds to LC_SUB_CLIENT.
class SubClientCommand < LoadCommand class SubClientCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the subclient name as an LCStr # @return [LCStr] the subclient name as an LCStr
attr_reader :sub_client attr_reader :sub_client
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -841,7 +871,8 @@ module MachO
# @return [Fixnum] the number of hints in the hint table # @return [Fixnum] the number of hints in the hint table
attr_reader :nhints attr_reader :nhints
# @return [MachO::TwolevelHintsCommand::TwolevelHintTable] the hint table # @return [TwolevelHintsTable]
# the hint table
attr_reader :table attr_reader :table
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -863,7 +894,7 @@ module MachO
# A representation of the two-level namespace lookup hints table exposed # A representation of the two-level namespace lookup hints table exposed
# by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`). # by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`).
class TwolevelHintsTable class TwolevelHintsTable
# @return [Array<MachO::TwoLevelHintsTable::TwoLevelHint>] all hints in the table # @return [Array<TwolevelHint>] all hints in the table
attr_reader :hints attr_reader :hints
# @param view [MachO::MachOView] the view into the current Mach-O # @param view [MachO::MachOView] the view into the current Mach-O
@ -921,7 +952,7 @@ module MachO
# be added to the current run path used to find @rpath prefixed dylibs. # be added to the current run path used to find @rpath prefixed dylibs.
# Corresponds to LC_RPATH. # Corresponds to LC_RPATH.
class RpathCommand < LoadCommand class RpathCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the path to add to the run path as an LCStr # @return [LCStr] the path to add to the run path as an LCStr
attr_reader :path attr_reader :path
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -938,20 +969,23 @@ module MachO
@path = LCStr.new(self, path) @path = LCStr.new(self, path)
end end
# @param context [MachO::LoadCcommand::SerializationContext] the context # @param context [SerializationContext] the context
# @return [String] the serialized fields of the load command # @return [String] the serialized fields of the load command
# @api private # @api private
def serialize(context) def serialize(context)
format = Utils.specialize_format(FORMAT, context.endianness) format = Utils.specialize_format(FORMAT, context.endianness)
string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :path => path.to_s) string_payload, string_offsets = Utils.pack_strings(SIZEOF,
context.alignment,
:path => path.to_s)
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload
end end
end end
# A load command representing the offsets and sizes of a blob of data in # A load command representing the offsets and sizes of a blob of data in
# the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, # the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE,
# LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS, and LC_LINKER_OPTIMIZATION_HINT. # LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
# LC_DYLIB_CODE_SIGN_DRS, and LC_LINKER_OPTIMIZATION_HINT.
class LinkeditDataCommand < LoadCommand class LinkeditDataCommand < LoadCommand
# @return [Fixnum] offset to the data in the __LINKEDIT segment # @return [Fixnum] offset to the data in the __LINKEDIT segment
attr_reader :dataoff attr_reader :dataoff
@ -1038,7 +1072,8 @@ module MachO
end end
# A load command containing the minimum OS version on which the binary # A load command containing the minimum OS version on which the binary
# was built to run. Corresponds to LC_VERSION_MIN_MACOSX and LC_VERSION_MIN_IPHONEOS. # was built to run. Corresponds to LC_VERSION_MIN_MACOSX and
# LC_VERSION_MIN_IPHONEOS.
class VersionMinCommand < LoadCommand class VersionMinCommand < LoadCommand
# @return [Fixnum] the version X.Y.Z packed as x16.y8.z8 # @return [Fixnum] the version X.Y.Z packed as x16.y8.z8
attr_reader :version attr_reader :version
@ -1247,9 +1282,9 @@ module MachO
end end
end end
# An obsolete load command containing a free format string table. Each string # An obsolete load command containing a free format string table. Each
# is null-terminated and the command is zero-padded to a multiple of 4. # string is null-terminated and the command is zero-padded to a multiple of
# Corresponds to LC_IDENT. # 4. Corresponds to LC_IDENT.
class IdentCommand < LoadCommand class IdentCommand < LoadCommand
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
@ -1263,7 +1298,7 @@ module MachO
# An obsolete load command containing the path to a file to be loaded into # An obsolete load command containing the path to a file to be loaded into
# memory. Corresponds to LC_FVMFILE. # memory. Corresponds to LC_FVMFILE.
class FvmfileCommand < LoadCommand class FvmfileCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the pathname of the file being loaded # @return [LCStr] the pathname of the file being loaded
attr_reader :name attr_reader :name
# @return [Fixnum] the virtual address being loaded at # @return [Fixnum] the virtual address being loaded at
@ -1284,10 +1319,10 @@ module MachO
end end
end end
# An obsolete load command containing the path to a library to be loaded into # An obsolete load command containing the path to a library to be loaded
# memory. Corresponds to LC_LOADFVMLIB and LC_IDFVMLIB. # into memory. Corresponds to LC_LOADFVMLIB and LC_IDFVMLIB.
class FvmlibCommand < LoadCommand class FvmlibCommand < LoadCommand
# @return [MachO::LoadCommand::LCStr] the library's target pathname # @return [LCStr] the library's target pathname
attr_reader :name attr_reader :name
# @return [Fixnum] the library's minor version number # @return [Fixnum] the library's minor version number
@ -1312,3 +1347,4 @@ module MachO
end end
end end
end end
end

View File

@ -1,27 +1,33 @@
require "forwardable"
module MachO module MachO
# Represents a Mach-O file, which contains a header and load commands # Represents a Mach-O file, which contains a header and load commands
# as well as binary executable instructions. Mach-O binaries are # as well as binary executable instructions. Mach-O binaries are
# architecture specific. # architecture specific.
# @see https://en.wikipedia.org/wiki/Mach-O # @see https://en.wikipedia.org/wiki/Mach-O
# @see MachO::FatFile # @see FatFile
class MachOFile class MachOFile
# @return [String] the filename loaded from, or nil if loaded from a binary string extend Forwardable
# @return [String] the filename loaded from, or nil if loaded from a binary
# string
attr_accessor :filename attr_accessor :filename
# @return [Symbol] the endianness of the file, :big or :little # @return [Symbol] the endianness of the file, :big or :little
attr_reader :endianness attr_reader :endianness
# @return [MachO::MachHeader] if the Mach-O is 32-bit # @return [Headers::MachHeader] if the Mach-O is 32-bit
# @return [MachO::MachHeader64] if the Mach-O is 64-bit # @return [Headers::MachHeader64] if the Mach-O is 64-bit
attr_reader :header attr_reader :header
# @return [Array<MachO::LoadCommand>] an array of the file's load commands # @return [Array<LoadCommands::LoadCommand>] an array of the file's load
# commands
# @note load commands are provided in order of ascending offset. # @note load commands are provided in order of ascending offset.
attr_reader :load_commands attr_reader :load_commands
# Creates a new MachOFile instance from a binary string. # Creates a new MachOFile instance from a binary string.
# @param bin [String] a binary string containing raw Mach-O data # @param bin [String] a binary string containing raw Mach-O data
# @return [MachO::MachOFile] a new MachOFile # @return [MachOFile] a new MachOFile
def self.new_from_bin(bin) def self.new_from_bin(bin)
instance = allocate instance = allocate
instance.initialize_from_bin(bin) instance.initialize_from_bin(bin)
@ -55,109 +61,63 @@ module MachO
@raw_data @raw_data
end end
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise # @!method magic
def magic32? # @return (see MachO::Headers::MachHeader#magic)
Utils.magic32?(header.magic) # @!method ncmds
end # @return (see MachO::Headers::MachHeader#ncmds)
# @!method sizeofcmds
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise # @return (see MachO::Headers::MachHeader#sizeofcmds)
def magic64? # @!method flags
Utils.magic64?(header.magic) # @return (see MachO::Headers::MachHeader#flags)
end # @!method object?
# @return (see MachO::Headers::MachHeader#object?)
# @return [Fixnum] the file's internal alignment # @!method executable?
def alignment # @return (see MachO::Headers::MachHeader#executable?)
magic32? ? 4 : 8 # @!method fvmlib?
end # @return (see MachO::Headers::MachHeader#fvmlib?)
# @!method core?
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise # @return (see MachO::Headers::MachHeader#core?)
def object? # @!method preload?
header.filetype == MH_OBJECT # @return (see MachO::Headers::MachHeader#preload?)
end # @!method dylib?
# @return (see MachO::Headers::MachHeader#dylib?)
# @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise # @!method dylinker?
def executable? # @return (see MachO::Headers::MachHeader#dylinker?)
header.filetype == MH_EXECUTE # @!method bundle?
end # @return (see MachO::Headers::MachHeader#bundle?)
# @!method dsym?
# @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise # @return (see MachO::Headers::MachHeader#dsym?)
def fvmlib? # @!method kext?
header.filetype == MH_FVMLIB # @return (see MachO::Headers::MachHeader#kext?)
end # @!method magic32?
# @return (see MachO::Headers::MachHeader#magic32?)
# @return [Boolean] true if the file is of type `MH_CORE`, false otherwise # @!method magic64?
def core? # @return (see MachO::Headers::MachHeader#magic64?)
header.filetype == MH_CORE # @!method alignment
end # @return (see MachO::Headers::MachHeader#alignment)
def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
# @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise :executable?, :fvmlib?, :core?, :preload?, :dylib?,
def preload? :dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
header.filetype == MH_PRELOAD :alignment
end
# @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
def dylib?
header.filetype == MH_DYLIB
end
# @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
def dylinker?
header.filetype == MH_DYLINKER
end
# @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
def bundle?
header.filetype == MH_BUNDLE
end
# @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
def dsym?
header.filetype == MH_DSYM
end
# @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
def kext?
header.filetype == MH_KEXT_BUNDLE
end
# @return [Fixnum] the file's magic number
def magic
header.magic
end
# @return [String] a string representation of the file's magic number # @return [String] a string representation of the file's magic number
def magic_string def magic_string
MH_MAGICS[magic] Headers::MH_MAGICS[magic]
end end
# @return [Symbol] a string representation of the Mach-O's filetype # @return [Symbol] a string representation of the Mach-O's filetype
def filetype def filetype
MH_FILETYPES[header.filetype] Headers::MH_FILETYPES[header.filetype]
end end
# @return [Symbol] a symbol representation of the Mach-O's CPU type # @return [Symbol] a symbol representation of the Mach-O's CPU type
def cputype def cputype
CPU_TYPES[header.cputype] Headers::CPU_TYPES[header.cputype]
end end
# @return [Symbol] a symbol representation of the Mach-O's CPU subtype # @return [Symbol] a symbol representation of the Mach-O's CPU subtype
def cpusubtype def cpusubtype
CPU_SUBTYPES[header.cputype][header.cpusubtype] Headers::CPU_SUBTYPES[header.cputype][header.cpusubtype]
end
# @return [Fixnum] the number of load commands in the Mach-O's header
def ncmds
header.ncmds
end
# @return [Fixnum] the size of all load commands, in bytes
def sizeofcmds
header.sizeofcmds
end
# @return [Fixnum] execution flags set by the linker
def flags
header.flags
end end
# All load commands of a given name. # All load commands of a given name.
@ -165,7 +125,8 @@ module MachO
# file.command("LC_LOAD_DYLIB") # file.command("LC_LOAD_DYLIB")
# file[:LC_LOAD_DYLIB] # file[:LC_LOAD_DYLIB]
# @param [String, Symbol] name the load command ID # @param [String, Symbol] name the load command ID
# @return [Array<MachO::LoadCommand>] an array of LoadCommands corresponding to `name` # @return [Array<LoadCommands::LoadCommand>] an array of load commands
# corresponding to `name`
def command(name) def command(name)
load_commands.select { |lc| lc.type == name.to_sym } load_commands.select { |lc| lc.type == name.to_sym }
end end
@ -174,16 +135,16 @@ module MachO
# Inserts a load command at the given offset. # Inserts a load command at the given offset.
# @param offset [Fixnum] the offset to insert at # @param offset [Fixnum] the offset to insert at
# @param lc [MachO::LoadCommand] the load command to insert # @param lc [LoadCommands::LoadCommand] the load command to insert
# @param options [Hash] # @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate # @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields # the instance fields
# @raise [MachO::OffsetInsertionError] if the offset is not in the load command region # @raise [OffsetInsertionError] if the offset is not in the load command region
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer # @raise [HeaderPadError] if the new command exceeds the header pad buffer
# @note Calling this method with an arbitrary offset in the load command # @note Calling this method with an arbitrary offset in the load command
# region **will leave the object in an inconsistent state**. # region **will leave the object in an inconsistent state**.
def insert_command(offset, lc, options = {}) def insert_command(offset, lc, options = {})
context = LoadCommand::SerializationContext.context_for(self) context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = lc.serialize(context) cmd_raw = lc.serialize(context)
if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
@ -207,14 +168,14 @@ module MachO
end end
# Replace a load command with another command in the Mach-O, preserving location. # Replace a load command with another command in the Mach-O, preserving location.
# @param old_lc [MachO::LoadCommand] the load command being replaced # @param old_lc [LoadCommands::LoadCommand] the load command being replaced
# @param new_lc [MachO::LoadCommand] the load command being added # @param new_lc [LoadCommands::LoadCommand] the load command being added
# @return [void] # @return [void]
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer # @raise [HeaderPadError] if the new command exceeds the header pad buffer
# @see {#insert_command} # @see #insert_command
# @note This is public, but methods like {#dylib_id=} should be preferred. # @note This is public, but methods like {#dylib_id=} should be preferred.
def replace_command(old_lc, new_lc) def replace_command(old_lc, new_lc)
context = LoadCommand::SerializationContext.context_for(self) context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = new_lc.serialize(context) cmd_raw = new_lc.serialize(context)
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
if header.class.bytesize + new_sizeofcmds > low_fileoff if header.class.bytesize + new_sizeofcmds > low_fileoff
@ -226,12 +187,12 @@ module MachO
end end
# Appends a new load command to the Mach-O. # Appends a new load command to the Mach-O.
# @param lc [MachO::LoadCommand] the load command being added # @param lc [LoadCommands::LoadCommand] the load command being added
# @param options [Hash] # @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate # @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields # the instance fields
# @return [void] # @return [void]
# @see {#insert_command} # @see #insert_command
# @note This is public, but methods like {#add_rpath} should be preferred. # @note This is public, but methods like {#add_rpath} should be preferred.
# Setting `repopulate` to false **will leave the instance in an # Setting `repopulate` to false **will leave the instance in an
# inconsistent state** unless {#populate_fields} is called **immediately** # inconsistent state** unless {#populate_fields} is called **immediately**
@ -241,7 +202,7 @@ module MachO
end end
# Delete a load command from the Mach-O. # Delete a load command from the Mach-O.
# @param lc [MachO::LoadCommand] the load command being deleted # @param lc [LoadCommands::LoadCommand] the load command being deleted
# @param options [Hash] # @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate # @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields # the instance fields
@ -275,14 +236,14 @@ module MachO
end end
# All load commands responsible for loading dylibs. # All load commands responsible for loading dylibs.
# @return [Array<MachO::DylibCommand>] an array of DylibCommands # @return [Array<LoadCommands::DylibCommand>] an array of DylibCommands
def dylib_load_commands def dylib_load_commands
load_commands.select { |lc| DYLIB_LOAD_COMMANDS.include?(lc.type) } load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) }
end end
# All segment load commands in the Mach-O. # All segment load commands in the Mach-O.
# @return [Array<MachO::SegmentCommand>] if the Mach-O is 32-bit # @return [Array<LoadCommands::SegmentCommand>] if the Mach-O is 32-bit
# @return [Array<MachO::SegmentCommand64>] if the Mach-O is 64-bit # @return [Array<LoadCommands::SegmentCommand64>] if the Mach-O is 64-bit
def segments def segments
if magic32? if magic32?
command(:LC_SEGMENT) command(:LC_SEGMENT)
@ -319,7 +280,7 @@ module MachO
old_lc = command(:LC_ID_DYLIB).first old_lc = command(:LC_ID_DYLIB).first
raise DylibIdMissingError unless old_lc raise DylibIdMissingError unless old_lc
new_lc = LoadCommand.create(:LC_ID_DYLIB, new_id, new_lc = LoadCommands::LoadCommand.create(:LC_ID_DYLIB, new_id,
old_lc.timestamp, old_lc.timestamp,
old_lc.current_version, old_lc.current_version,
old_lc.compatibility_version) old_lc.compatibility_version)
@ -341,19 +302,19 @@ module MachO
# Changes the shared library `old_name` to `new_name` # Changes the shared library `old_name` to `new_name`
# @example # @example
# file.change_install_name("/usr/lib/libWhatever.dylib", "/usr/local/lib/libWhatever2.dylib") # file.change_install_name("abc.dylib", "def.dylib")
# @param old_name [String] the shared library's old name # @param old_name [String] the shared library's old name
# @param new_name [String] the shared library's new name # @param new_name [String] the shared library's new name
# @param _options [Hash] # @param _options [Hash]
# @return [void] # @return [void]
# @raise [MachO::DylibUnknownError] if no shared library has the old name # @raise [DylibUnknownError] if no shared library has the old name
# @note `_options` is currently unused and is provided for signature # @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#change_install_name} # compatibility with {MachO::FatFile#change_install_name}
def change_install_name(old_name, new_name, _options = {}) def change_install_name(old_name, new_name, _options = {})
old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name } old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name }
raise DylibUnknownError, old_name if old_lc.nil? raise DylibUnknownError, old_name if old_lc.nil?
new_lc = LoadCommand.create(old_lc.type, new_name, new_lc = LoadCommands::LoadCommand.create(old_lc.type, new_name,
old_lc.timestamp, old_lc.timestamp,
old_lc.current_version, old_lc.current_version,
old_lc.compatibility_version) old_lc.compatibility_version)
@ -376,8 +337,8 @@ module MachO
# @param new_path [String] the new runtime path # @param new_path [String] the new runtime path
# @param _options [Hash] # @param _options [Hash]
# @return [void] # @return [void]
# @raise [MachO::RpathUnknownError] if no such old runtime path exists # @raise [RpathUnknownError] if no such old runtime path exists
# @raise [MachO::RpathExistsError] if the new runtime path already exists # @raise [RpathExistsError] if the new runtime path already exists
# @note `_options` is currently unused and is provided for signature # @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#change_rpath} # compatibility with {MachO::FatFile#change_rpath}
def change_rpath(old_path, new_path, _options = {}) def change_rpath(old_path, new_path, _options = {})
@ -385,7 +346,7 @@ module MachO
raise RpathUnknownError, old_path if old_lc.nil? raise RpathUnknownError, old_path if old_lc.nil?
raise RpathExistsError, new_path if rpaths.include?(new_path) raise RpathExistsError, new_path if rpaths.include?(new_path)
new_lc = LoadCommand.create(:LC_RPATH, new_path) new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
delete_rpath(old_path) delete_rpath(old_path)
insert_command(old_lc.view.offset, new_lc) insert_command(old_lc.view.offset, new_lc)
@ -399,13 +360,13 @@ module MachO
# @param path [String] the new runtime path # @param path [String] the new runtime path
# @param _options [Hash] # @param _options [Hash]
# @return [void] # @return [void]
# @raise [MachO::RpathExistsError] if the runtime path already exists # @raise [RpathExistsError] if the runtime path already exists
# @note `_options` is currently unused and is provided for signature # @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#add_rpath} # compatibility with {MachO::FatFile#add_rpath}
def add_rpath(path, _options = {}) def add_rpath(path, _options = {})
raise RpathExistsError, path if rpaths.include?(path) raise RpathExistsError, path if rpaths.include?(path)
rpath_cmd = LoadCommand.create(:LC_RPATH, path) rpath_cmd = LoadCommands::LoadCommand.create(:LC_RPATH, path)
add_command(rpath_cmd) add_command(rpath_cmd)
end end
@ -417,7 +378,7 @@ module MachO
# @param path [String] the runtime path to delete # @param path [String] the runtime path to delete
# @param _options [Hash] # @param _options [Hash]
# @return void # @return void
# @raise [MachO::RpathUnknownError] if no such runtime path exists # @raise [RpathUnknownError] if no such runtime path exists
# @note `_options` is currently unused and is provided for signature # @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#delete_rpath} # compatibility with {MachO::FatFile#delete_rpath}
def delete_rpath(path, _options = {}) def delete_rpath(path, _options = {})
@ -431,15 +392,6 @@ module MachO
populate_fields populate_fields
end 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)
segment.sections
end
# Write all Mach-O data to the given filename. # Write all Mach-O data to the given filename.
# @param filename [String] the file to write to # @param filename [String] the file to write to
# @return [void] # @return [void]
@ -449,7 +401,7 @@ module MachO
# 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.
# @return [void] # @return [void]
# @raise [MachO::MachOError] if the instance was initialized without a file # @raise [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? if @filename.nil?
@ -462,16 +414,16 @@ module MachO
private private
# 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 [Headers::MachHeader] if the Mach-O is 32-bit
# @return [MachO::MachHeader64] if the Mach-O is 64-bit # @return [Headers::MachHeader64] if the Mach-O is 64-bit
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header # @raise [TruncatedFileError] if the file is too small to have a valid header
# @api private # @api private
def populate_mach_header def populate_mach_header
# the smallest Mach-O header is 28 bytes # the smallest Mach-O header is 28 bytes
raise TruncatedFileError if @raw_data.size < 28 raise TruncatedFileError if @raw_data.size < 28
magic = populate_and_check_magic magic = populate_and_check_magic
mh_klass = Utils.magic32?(magic) ? MachHeader : MachHeader64 mh_klass = Utils.magic32?(magic) ? Headers::MachHeader : Headers::MachHeader64
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize]) mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
check_cputype(mh.cputype) check_cputype(mh.cputype)
@ -483,8 +435,8 @@ module MachO
# Read just the file's magic number and check its validity. # 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 [MagicError] if the magic is not valid Mach-O magic
# @raise [MachO::FatBinaryError] if the magic is for a Fat file # @raise [FatBinaryError] if the magic is for a Fat file
# @api private # @api private
def populate_and_check_magic def populate_and_check_magic
magic = @raw_data[0..3].unpack("N").first magic = @raw_data[0..3].unpack("N").first
@ -499,32 +451,32 @@ module MachO
# Check the file's CPU type. # Check the file's CPU type.
# @param cputype [Fixnum] the CPU type # @param cputype [Fixnum] the CPU type
# @raise [MachO::CPUTypeError] if the CPU type is unknown # @raise [CPUTypeError] if the CPU type is unknown
# @api private # @api private
def check_cputype(cputype) def check_cputype(cputype)
raise CPUTypeError, cputype unless CPU_TYPES.key?(cputype) raise CPUTypeError, cputype unless Headers::CPU_TYPES.key?(cputype)
end end
# Check the file's CPU type/subtype pair. # Check the file's CPU type/subtype pair.
# @param cpusubtype [Fixnum] the CPU subtype # @param cpusubtype [Fixnum] the CPU subtype
# @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown # @raise [CPUSubtypeError] if the CPU sub-type is unknown
# @api private # @api private
def check_cpusubtype(cputype, cpusubtype) def check_cpusubtype(cputype, cpusubtype)
# Only check sub-type w/o capability bits (see `populate_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) raise CPUSubtypeError.new(cputype, cpusubtype) unless Headers::CPU_SUBTYPES[cputype].key?(cpusubtype)
end end
# Check the file's type. # Check the file's type.
# @param filetype [Fixnum] the file type # @param filetype [Fixnum] the file type
# @raise [MachO::FiletypeError] if the file type is unknown # @raise [FiletypeError] if the file type is unknown
# @api private # @api private
def check_filetype(filetype) def check_filetype(filetype)
raise FiletypeError, filetype unless MH_FILETYPES.key?(filetype) raise FiletypeError, filetype unless Headers::MH_FILETYPES.key?(filetype)
end end
# All load commands in the file. # All load commands in the file.
# @return [Array<MachO::LoadCommand>] an array of load commands # @return [Array<LoadCommands::LoadCommand>] an array of load commands
# @raise [MachO::LoadCommandError] if an unknown load command is encountered # @raise [LoadCommandError] if an unknown load command is encountered
# @api private # @api private
def populate_load_commands def populate_load_commands
offset = header.class.bytesize offset = header.class.bytesize
@ -533,13 +485,13 @@ module MachO
header.ncmds.times do header.ncmds.times do
fmt = Utils.specialize_format("L=", endianness) fmt = Utils.specialize_format("L=", endianness)
cmd = @raw_data.slice(offset, 4).unpack(fmt).first cmd = @raw_data.slice(offset, 4).unpack(fmt).first
cmd_sym = LOAD_COMMANDS[cmd] cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
raise LoadCommandError, cmd if cmd_sym.nil? raise LoadCommandError, cmd if cmd_sym.nil?
# why do I do this? i don't like declaring constants below # why do I do this? i don't like declaring constants below
# classes, and i need them to resolve... # classes, and i need them to resolve...
klass = MachO.const_get LC_STRUCTURES[cmd_sym] klass = LoadCommands.const_get LoadCommands::LC_STRUCTURES[cmd_sym]
view = MachOView.new(@raw_data, endianness, offset) view = MachOView.new(@raw_data, endianness, offset)
command = klass.new_from_bin(view) command = klass.new_from_bin(view)

View File

@ -1,25 +0,0 @@
module MachO
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened
# @return [MachO::MachOFile] if the file is a Mach-O
# @return [MachO::FatFile] if the file is a Fat file
# @raise [ArgumentError] if the given file does not exist
# @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, "#{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 Utils.fat_magic?(magic)
file = FatFile.new(filename)
elsif Utils.magic?(magic)
file = MachOFile.new(filename)
else
raise MagicError, magic
end
file
end
end

View File

@ -1,4 +1,6 @@
module MachO module MachO
# Classes and constants for parsing sections in Mach-O binaries.
module Sections
# type mask # type mask
SECTION_TYPE = 0x000000ff SECTION_TYPE = 0x000000ff
@ -70,7 +72,8 @@ module MachO
# @return [String] the name of the section, including null pad bytes # @return [String] the name of the section, including null pad bytes
attr_reader :sectname attr_reader :sectname
# @return [String] the name of the segment's section, including null pad bytes # @return [String] the name of the segment's section, including null
# pad bytes
attr_reader :segname attr_reader :segname
# @return [Fixnum] the memory address of the section # @return [Fixnum] the memory address of the section
@ -122,17 +125,19 @@ module MachO
@reserved2 = reserved2 @reserved2 = reserved2
end end
# @return [String] the section's name, with any trailing NULL characters removed # @return [String] the section's name, with any trailing NULL characters
# removed
def section_name def section_name
sectname.delete("\x00") sectname.delete("\x00")
end end
# @return [String] the parent segment's name, with any trailing NULL characters removed # @return [String] the parent segment's name, with any trailing NULL
# characters removed
def segment_name def segment_name
segname.delete("\x00") segname.delete("\x00")
end end
# @return [Boolean] true if the section has no contents (i.e, `size` is 0) # @return [Boolean] whether the section is empty (i.e, {size} is 0)
def empty? def empty?
size.zero? size.zero?
end end
@ -140,7 +145,7 @@ module MachO
# @example # @example
# puts "this section is regular" if sect.flag?(:S_REGULAR) # puts "this section is regular" if sect.flag?(:S_REGULAR)
# @param flag [Symbol] a section flag symbol # @param flag [Symbol] a section flag symbol
# @return [Boolean] true if `flag` is present in the section's flag field # @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?
@ -168,3 +173,4 @@ module MachO
end end
end end
end end
end

View File

@ -19,7 +19,7 @@ module MachO
# @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 # @param bin [String] the string to be unpacked into the new structure
# @return [MachO::MachOStructure] a new MachOStructure initialized with `bin` # @return [MachO::MachOStructure] the resulting structure
# @api private # @api private
def self.new_from_bin(endianness, bin) def self.new_from_bin(endianness, bin)
format = Utils.specialize_format(self::FORMAT, endianness) format = Utils.specialize_format(self::FORMAT, endianness)

View File

@ -1,5 +1,6 @@
module MachO module MachO
# A collection of convenient methods for common operations on Mach-O and Fat binaries. # A collection of convenient methods for common operations on Mach-O and Fat
# binaries.
module Tools module Tools
# @param filename [String] the Mach-O or Fat binary being read # @param filename [String] the Mach-O or Fat binary being read
# @return [Array<String>] an array of all dylibs linked to the binary # @return [Array<String>] an array of all dylibs linked to the binary
@ -9,7 +10,8 @@ module MachO
file.linked_dylibs file.linked_dylibs
end end
# Changes the dylib ID of a Mach-O or Fat binary, overwriting the source file. # 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 filename [String] the Mach-O or Fat binary being modified
# @param new_id [String] the new dylib ID for the binary # @param new_id [String] the new dylib ID for the binary
# @param options [Hash] # @param options [Hash]
@ -23,7 +25,8 @@ module MachO
file.write! file.write!
end end
# Changes a shared library install name in a Mach-O or Fat binary, overwriting the source file. # Changes a shared library install name in a Mach-O or Fat binary,
# overwriting the source file.
# @param filename [String] the Mach-O or Fat binary being modified # @param filename [String] the Mach-O or Fat binary being modified
# @param old_name [String] the old shared library name # @param old_name [String] the old shared library name
# @param new_name [String] the new shared library name # @param new_name [String] the new shared library name
@ -38,7 +41,8 @@ module MachO
file.write! file.write!
end end
# Changes a runtime path in a Mach-O or Fat binary, overwriting the source file. # Changes a runtime path in a Mach-O or Fat binary, overwriting the source
# file.
# @param filename [String] the Mach-O or Fat binary being modified # @param filename [String] the Mach-O or Fat binary being modified
# @param old_path [String] the old runtime path # @param old_path [String] the old runtime path
# @param new_path [String] the new runtime path # @param new_path [String] the new runtime path
@ -67,7 +71,8 @@ module MachO
file.write! file.write!
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.
# @param filename [String] the Mach-O or Fat binary being modified # @param filename [String] the Mach-O or Fat binary being modified
# @param old_path [String] the old runtime path # @param old_path [String] the old runtime path
# @param options [Hash] # @param options [Hash]
@ -80,5 +85,24 @@ module MachO
file.delete_rpath(old_path, options) file.delete_rpath(old_path, options)
file.write! file.write!
end end
# Merge multiple Mach-Os into one universal (Fat) binary.
# @param filename [String] the fat binary to create
# @param files [Array<MachO::MachOFile, MachO::FatFile>] the files to merge
# @return [void]
def self.merge_machos(filename, *files)
machos = files.map do |file|
macho = MachO.open(file)
case macho
when MachO::MachOFile
macho
else
macho.machos
end
end.flatten
fat_macho = MachO::FatFile.new_from_machos(*machos)
fat_macho.write(filename)
end
end end
end end

View File

@ -5,7 +5,7 @@ module MachO
# @param value [Fixnum] the number being rounded # @param value [Fixnum] the number being rounded
# @param round [Fixnum] the number being rounded with # @param round [Fixnum] the number being rounded with
# @return [Fixnum] the rounded value # @return [Fixnum] the rounded value
# @see https://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c # @see http://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c
def self.round(value, round) def self.round(value, round)
round -= 1 round -= 1
value += round value += round
@ -13,7 +13,8 @@ module MachO
value value
end end
# Returns the number of bytes needed to pad the given size to the given alignment. # Returns the number of bytes needed to pad the given size to the given
# alignment.
# @param size [Fixnum] the unpadded size # @param size [Fixnum] the unpadded size
# @param alignment [Fixnum] the number to alignment the size with # @param alignment [Fixnum] the number to alignment the size with
# @return [Fixnum] the number of pad bytes required # @return [Fixnum] the number of pad bytes required
@ -21,7 +22,8 @@ module MachO
round(size, alignment) - size round(size, alignment) - size
end end
# Converts an abstract (native-endian) String#unpack format to big or little. # Converts an abstract (native-endian) String#unpack format to big or
# little.
# @param format [String] the format string being converted # @param format [String] the format string being converted
# @param endianness [Symbol] either `:big` or `:little` # @param endianness [Symbol] either `:big` or `:little`
# @return [String] the converted string # @return [String] the converted string
@ -31,7 +33,8 @@ module MachO
end end
# Packs tagged strings into an aligned payload. # Packs tagged strings into an aligned payload.
# @param fixed_offset [Fixnum] the baseline offset for the first packed string # @param fixed_offset [Fixnum] the baseline offset for the first packed
# string
# @param alignment [Fixnum] the alignment value to use for packing # @param alignment [Fixnum] the alignment value to use for packing
# @param strings [Hash] the labeled strings to pack # @param strings [Hash] the labeled strings to pack
# @return [Array<String, Hash>] the packed string and labeled offsets # @return [Array<String, Hash>] the packed string and labeled offsets
@ -53,44 +56,44 @@ module MachO
# Compares the given number to valid Mach-O magic numbers. # Compares the given number to valid Mach-O magic numbers.
# @param num [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid Mach-O magic number, false otherwise # @return [Boolean] whether `num` is a valid Mach-O magic number
def self.magic?(num) def self.magic?(num)
MH_MAGICS.key?(num) Headers::MH_MAGICS.key?(num)
end end
# Compares the given number to valid Fat magic numbers. # Compares the given number to valid Fat magic numbers.
# @param num [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid Fat magic number, false otherwise # @return [Boolean] whether `num` is a valid Fat magic number
def self.fat_magic?(num) def self.fat_magic?(num)
num == FAT_MAGIC num == Headers::FAT_MAGIC
end 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 [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise # @return [Boolean] whether `num` is a valid 32-bit magic number
def self.magic32?(num) def self.magic32?(num)
num == MH_MAGIC || num == MH_CIGAM num == Headers::MH_MAGIC || num == Headers::MH_CIGAM
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 [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise # @return [Boolean] whether `num` is a valid 64-bit magic number
def self.magic64?(num) def self.magic64?(num)
num == MH_MAGIC_64 || num == MH_CIGAM_64 num == Headers::MH_MAGIC_64 || num == Headers::MH_CIGAM_64
end end
# Compares the given number to valid little-endian magic numbers. # Compares the given number to valid little-endian magic numbers.
# @param num [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise # @return [Boolean] whether `num` is a valid little-endian magic number
def self.little_magic?(num) def self.little_magic?(num)
num == MH_CIGAM || num == MH_CIGAM_64 num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
end end
# Compares the given number to valid big-endian magic numbers. # Compares the given number to valid big-endian magic numbers.
# @param num [Fixnum] the number being checked # @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise # @return [Boolean] whether `num` is a valid big-endian magic number
def self.big_magic?(num) def self.big_magic?(num)
num == MH_CIGAM || num == MH_CIGAM_64 num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
end end
end end
end end