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
* [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:
@ -33,7 +33,7 @@ Vendored Dependencies
### ruby-macho
> 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
> 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/macho_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/utils"
require "#{File.dirname(__FILE__)}/macho/tools"
@ -13,5 +12,29 @@ require "#{File.dirname(__FILE__)}/macho/tools"
# The primary namespace for ruby-macho.
module MachO
# 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

View File

@ -80,7 +80,8 @@ module MachO
# @param cputype [Fixnum] the CPU type of the unknown pair
# @param cpusubtype [Fixnum] the CPU sub-type of the unknown pair
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
@ -108,13 +109,15 @@ module MachO
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
# @param cmd_sym [Symbol] the load command's symbol
# @param expected_arity [Fixnum] the number of arguments expected
# @param actual_arity [Fixnum] the number of arguments received
def initialize(cmd_sym, expected_arity, actual_arity)
super "Expected #{expected_arity} arguments for #{cmd_sym} creation, got #{actual_arity}"
super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
" got #{actual_arity}"
end
end
@ -130,7 +133,8 @@ module MachO
class LCStrMalformedError < MachOError
# @param lc [MachO::LoadCommand] the load command containing the string
def initialize(lc)
super "Load command #{lc.type} at offset #{lc.view.offset} contains a malformed string"
super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
" malformed string"
end
end

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,33 @@
require "forwardable"
module MachO
# Represents a Mach-O file, which contains a header and load commands
# as well as binary executable instructions. Mach-O binaries are
# architecture specific.
# @see https://en.wikipedia.org/wiki/Mach-O
# @see MachO::FatFile
# @see FatFile
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
# @return [Symbol] the endianness of the file, :big or :little
attr_reader :endianness
# @return [MachO::MachHeader] if the Mach-O is 32-bit
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
# @return [Headers::MachHeader] if the Mach-O is 32-bit
# @return [Headers::MachHeader64] if the Mach-O is 64-bit
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.
attr_reader :load_commands
# Creates a new MachOFile instance from a binary string.
# @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)
instance = allocate
instance.initialize_from_bin(bin)
@ -55,109 +61,63 @@ module MachO
@raw_data
end
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
def magic32?
Utils.magic32?(header.magic)
end
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
def magic64?
Utils.magic64?(header.magic)
end
# @return [Fixnum] the file's internal alignment
def alignment
magic32? ? 4 : 8
end
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
def object?
header.filetype == MH_OBJECT
end
# @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise
def executable?
header.filetype == MH_EXECUTE
end
# @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
def fvmlib?
header.filetype == MH_FVMLIB
end
# @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
def core?
header.filetype == MH_CORE
end
# @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
def preload?
header.filetype == MH_PRELOAD
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
# @!method magic
# @return (see MachO::Headers::MachHeader#magic)
# @!method ncmds
# @return (see MachO::Headers::MachHeader#ncmds)
# @!method sizeofcmds
# @return (see MachO::Headers::MachHeader#sizeofcmds)
# @!method flags
# @return (see MachO::Headers::MachHeader#flags)
# @!method object?
# @return (see MachO::Headers::MachHeader#object?)
# @!method executable?
# @return (see MachO::Headers::MachHeader#executable?)
# @!method fvmlib?
# @return (see MachO::Headers::MachHeader#fvmlib?)
# @!method core?
# @return (see MachO::Headers::MachHeader#core?)
# @!method preload?
# @return (see MachO::Headers::MachHeader#preload?)
# @!method dylib?
# @return (see MachO::Headers::MachHeader#dylib?)
# @!method dylinker?
# @return (see MachO::Headers::MachHeader#dylinker?)
# @!method bundle?
# @return (see MachO::Headers::MachHeader#bundle?)
# @!method dsym?
# @return (see MachO::Headers::MachHeader#dsym?)
# @!method kext?
# @return (see MachO::Headers::MachHeader#kext?)
# @!method magic32?
# @return (see MachO::Headers::MachHeader#magic32?)
# @!method magic64?
# @return (see MachO::Headers::MachHeader#magic64?)
# @!method alignment
# @return (see MachO::Headers::MachHeader#alignment)
def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment
# @return [String] a string representation of the file's magic number
def magic_string
MH_MAGICS[magic]
Headers::MH_MAGICS[magic]
end
# @return [Symbol] a string representation of the Mach-O's filetype
def filetype
MH_FILETYPES[header.filetype]
Headers::MH_FILETYPES[header.filetype]
end
# @return [Symbol] a symbol representation of the Mach-O's CPU type
def cputype
CPU_TYPES[header.cputype]
Headers::CPU_TYPES[header.cputype]
end
# @return [Symbol] a symbol representation of the Mach-O's CPU subtype
def cpusubtype
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
Headers::CPU_SUBTYPES[header.cputype][header.cpusubtype]
end
# All load commands of a given name.
@ -165,7 +125,8 @@ module MachO
# file.command("LC_LOAD_DYLIB")
# file[:LC_LOAD_DYLIB]
# @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)
load_commands.select { |lc| lc.type == name.to_sym }
end
@ -174,16 +135,16 @@ module MachO
# Inserts a load command at the given offset.
# @param offset [Fixnum] the offset to insert at
# @param lc [MachO::LoadCommand] the load command to insert
# @param lc [LoadCommands::LoadCommand] the load command to insert
# @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields
# @raise [MachO::OffsetInsertionError] if the offset is not in the load command region
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer
# @raise [OffsetInsertionError] if the offset is not in the load command region
# @raise [HeaderPadError] if the new command exceeds the header pad buffer
# @note Calling this method with an arbitrary offset in the load command
# region **will leave the object in an inconsistent state**.
def insert_command(offset, lc, options = {})
context = LoadCommand::SerializationContext.context_for(self)
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = lc.serialize(context)
if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
@ -207,14 +168,14 @@ module MachO
end
# Replace a load command with another command in the Mach-O, preserving location.
# @param old_lc [MachO::LoadCommand] the load command being replaced
# @param new_lc [MachO::LoadCommand] the load command being added
# @param old_lc [LoadCommands::LoadCommand] the load command being replaced
# @param new_lc [LoadCommands::LoadCommand] the load command being added
# @return [void]
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer
# @see {#insert_command}
# @raise [HeaderPadError] if the new command exceeds the header pad buffer
# @see #insert_command
# @note This is public, but methods like {#dylib_id=} should be preferred.
def replace_command(old_lc, new_lc)
context = LoadCommand::SerializationContext.context_for(self)
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = new_lc.serialize(context)
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
if header.class.bytesize + new_sizeofcmds > low_fileoff
@ -226,12 +187,12 @@ module MachO
end
# 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]
# @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields
# @return [void]
# @see {#insert_command}
# @see #insert_command
# @note This is public, but methods like {#add_rpath} should be preferred.
# Setting `repopulate` to false **will leave the instance in an
# inconsistent state** unless {#populate_fields} is called **immediately**
@ -241,7 +202,7 @@ module MachO
end
# 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]
# @option options [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields
@ -275,14 +236,14 @@ module MachO
end
# 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
load_commands.select { |lc| DYLIB_LOAD_COMMANDS.include?(lc.type) }
load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) }
end
# All segment load commands in the Mach-O.
# @return [Array<MachO::SegmentCommand>] if the Mach-O is 32-bit
# @return [Array<MachO::SegmentCommand64>] if the Mach-O is 64-bit
# @return [Array<LoadCommands::SegmentCommand>] if the Mach-O is 32-bit
# @return [Array<LoadCommands::SegmentCommand64>] if the Mach-O is 64-bit
def segments
if magic32?
command(:LC_SEGMENT)
@ -319,10 +280,10 @@ module MachO
old_lc = command(:LC_ID_DYLIB).first
raise DylibIdMissingError unless old_lc
new_lc = LoadCommand.create(:LC_ID_DYLIB, new_id,
old_lc.timestamp,
old_lc.current_version,
old_lc.compatibility_version)
new_lc = LoadCommands::LoadCommand.create(:LC_ID_DYLIB, new_id,
old_lc.timestamp,
old_lc.current_version,
old_lc.compatibility_version)
replace_command(old_lc, new_lc)
end
@ -341,22 +302,22 @@ module MachO
# Changes the shared library `old_name` to `new_name`
# @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 new_name [String] the shared library's new name
# @param _options [Hash]
# @return [void]
# @raise [MachO::DylibUnknownError] if no shared library has the old name
# @raise [DylibUnknownError] if no shared library has the old name
# @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#change_install_name}
def change_install_name(old_name, new_name, _options = {})
old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name }
raise DylibUnknownError, old_name if old_lc.nil?
new_lc = LoadCommand.create(old_lc.type, new_name,
old_lc.timestamp,
old_lc.current_version,
old_lc.compatibility_version)
new_lc = LoadCommands::LoadCommand.create(old_lc.type, new_name,
old_lc.timestamp,
old_lc.current_version,
old_lc.compatibility_version)
replace_command(old_lc, new_lc)
end
@ -376,8 +337,8 @@ module MachO
# @param new_path [String] the new runtime path
# @param _options [Hash]
# @return [void]
# @raise [MachO::RpathUnknownError] if no such old runtime path exists
# @raise [MachO::RpathExistsError] if the new runtime path already exists
# @raise [RpathUnknownError] if no such old runtime path exists
# @raise [RpathExistsError] if the new runtime path already exists
# @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#change_rpath}
def change_rpath(old_path, new_path, _options = {})
@ -385,7 +346,7 @@ module MachO
raise RpathUnknownError, old_path if old_lc.nil?
raise RpathExistsError, new_path if rpaths.include?(new_path)
new_lc = LoadCommand.create(:LC_RPATH, new_path)
new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
delete_rpath(old_path)
insert_command(old_lc.view.offset, new_lc)
@ -399,13 +360,13 @@ module MachO
# @param path [String] the new runtime path
# @param _options [Hash]
# @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
# compatibility with {MachO::FatFile#add_rpath}
def add_rpath(path, _options = {})
raise RpathExistsError, path if rpaths.include?(path)
rpath_cmd = LoadCommand.create(:LC_RPATH, path)
rpath_cmd = LoadCommands::LoadCommand.create(:LC_RPATH, path)
add_command(rpath_cmd)
end
@ -417,7 +378,7 @@ module MachO
# @param path [String] the runtime path to delete
# @param _options [Hash]
# @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
# compatibility with {MachO::FatFile#delete_rpath}
def delete_rpath(path, _options = {})
@ -431,15 +392,6 @@ module MachO
populate_fields
end
# All sections of the segment `segment`.
# @param segment [MachO::SegmentCommand, MachO::SegmentCommand64] the segment being inspected
# @return [Array<MachO::Section>] if the Mach-O is 32-bit
# @return [Array<MachO::Section64>] if the Mach-O is 64-bit
# @deprecated use {MachO::SegmentCommand#sections} instead
def sections(segment)
segment.sections
end
# Write all Mach-O data to the given filename.
# @param filename [String] the file to write to
# @return [void]
@ -449,7 +401,7 @@ module MachO
# Write all Mach-O data to the file used to initialize the instance.
# @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!
def write!
if @filename.nil?
@ -462,16 +414,16 @@ module MachO
private
# The file's Mach-O header structure.
# @return [MachO::MachHeader] if the Mach-O is 32-bit
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
# @return [Headers::MachHeader] if the Mach-O is 32-bit
# @return [Headers::MachHeader64] if the Mach-O is 64-bit
# @raise [TruncatedFileError] if the file is too small to have a valid header
# @api private
def populate_mach_header
# the smallest Mach-O header is 28 bytes
raise TruncatedFileError if @raw_data.size < 28
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])
check_cputype(mh.cputype)
@ -483,8 +435,8 @@ module MachO
# Read just the file's magic number and check its validity.
# @return [Fixnum] the magic
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic
# @raise [MachO::FatBinaryError] if the magic is for a Fat file
# @raise [MagicError] if the magic is not valid Mach-O magic
# @raise [FatBinaryError] if the magic is for a Fat file
# @api private
def populate_and_check_magic
magic = @raw_data[0..3].unpack("N").first
@ -499,32 +451,32 @@ module MachO
# Check the file's 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
def check_cputype(cputype)
raise CPUTypeError, cputype unless CPU_TYPES.key?(cputype)
raise CPUTypeError, cputype unless Headers::CPU_TYPES.key?(cputype)
end
# Check the file's CPU type/subtype pair.
# @param cpusubtype [Fixnum] the CPU subtype
# @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown
# @raise [CPUSubtypeError] if the CPU sub-type is unknown
# @api private
def check_cpusubtype(cputype, cpusubtype)
# 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
# Check the file's 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
def check_filetype(filetype)
raise FiletypeError, filetype unless MH_FILETYPES.key?(filetype)
raise FiletypeError, filetype unless Headers::MH_FILETYPES.key?(filetype)
end
# All load commands in the file.
# @return [Array<MachO::LoadCommand>] an array of load commands
# @raise [MachO::LoadCommandError] if an unknown load command is encountered
# @return [Array<LoadCommands::LoadCommand>] an array of load commands
# @raise [LoadCommandError] if an unknown load command is encountered
# @api private
def populate_load_commands
offset = header.class.bytesize
@ -533,13 +485,13 @@ module MachO
header.ncmds.times do
fmt = Utils.specialize_format("L=", endianness)
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?
# why do I do this? i don't like declaring constants below
# classes, and i need them to resolve...
klass = MachO.const_get LC_STRUCTURES[cmd_sym]
klass = LoadCommands.const_get LoadCommands::LC_STRUCTURES[cmd_sym]
view = MachOView.new(@raw_data, endianness, offset)
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,170 +1,176 @@
module MachO
# type mask
SECTION_TYPE = 0x000000ff
# Classes and constants for parsing sections in Mach-O binaries.
module Sections
# type mask
SECTION_TYPE = 0x000000ff
# attributes mask
SECTION_ATTRIBUTES = 0xffffff00
# attributes mask
SECTION_ATTRIBUTES = 0xffffff00
# user settable attributes mask
SECTION_ATTRIBUTES_USR = 0xff000000
# user settable attributes mask
SECTION_ATTRIBUTES_USR = 0xff000000
# system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00
# association of section flag symbols to values
# @api private
SECTION_FLAGS = {
:S_REGULAR => 0x0,
:S_ZEROFILL => 0x1,
:S_CSTRING_LITERALS => 0x2,
:S_4BYTE_LITERALS => 0x3,
:S_8BYTE_LITERALS => 0x4,
:S_LITERAL_POINTERS => 0x5,
:S_NON_LAZY_SYMBOL_POINTERS => 0x6,
:S_LAZY_SYMBOL_POINTERS => 0x7,
:S_SYMBOL_STUBS => 0x8,
:S_MOD_INIT_FUNC_POINTERS => 0x9,
:S_MOD_TERM_FUNC_POINTERS => 0xa,
:S_COALESCED => 0xb,
:S_GB_ZEROFILE => 0xc,
:S_INTERPOSING => 0xd,
:S_16BYTE_LITERALS => 0xe,
:S_DTRACE_DOF => 0xf,
:S_LAZY_DYLIB_SYMBOL_POINTERS => 0x10,
:S_THREAD_LOCAL_REGULAR => 0x11,
:S_THREAD_LOCAL_ZEROFILL => 0x12,
:S_THREAD_LOCAL_VARIABLES => 0x13,
:S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
:S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
:S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
:S_ATTR_NO_TOC => 0x40000000,
:S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
:S_ATTR_NO_DEAD_STRIP => 0x10000000,
:S_ATTR_LIVE_SUPPORT => 0x08000000,
:S_ATTR_SELF_MODIFYING_CODE => 0x04000000,
:S_ATTR_DEBUG => 0x02000000,
:S_ATTR_SOME_INSTRUCTIONS => 0x00000400,
:S_ATTR_EXT_RELOC => 0x00000200,
:S_ATTR_LOC_RELOC => 0x00000100,
}.freeze
# association of section name symbols to names
# @api private
SECTION_NAMES = {
:SECT_TEXT => "__text",
:SECT_FVMLIB_INIT0 => "__fvmlib_init0",
:SECT_FVMLIB_INIT1 => "__fvmlib_init1",
:SECT_DATA => "__data",
:SECT_BSS => "__bss",
:SECT_COMMON => "__common",
:SECT_OBJC_SYMBOLS => "__symbol_table",
:SECT_OBJC_MODULES => "__module_info",
:SECT_OBJC_STRINGS => "__selector_strs",
:SECT_OBJC_REFS => "__selector_refs",
:SECT_ICON_HEADER => "__header",
:SECT_ICON_TIFF => "__tiff",
}.freeze
# Represents a section of a segment for 32-bit architectures.
class Section < MachOStructure
# @return [String] the name of the section, including null pad bytes
attr_reader :sectname
# @return [String] the name of the segment's section, including null pad bytes
attr_reader :segname
# @return [Fixnum] the memory address of the section
attr_reader :addr
# @return [Fixnum] the size, in bytes, of the section
attr_reader :size
# @return [Fixnum] the file offset of the section
attr_reader :offset
# @return [Fixnum] the section alignment (power of 2) of the section
attr_reader :align
# @return [Fixnum] the file offset of the section's relocation entries
attr_reader :reloff
# @return [Fixnum] the number of relocation entries
attr_reader :nreloc
# @return [Fixnum] flags for type and attributes of the section
attr_reader :flags
# @return [void] reserved (for offset or index)
attr_reader :reserved1
# @return [void] reserved (for count or sizeof)
attr_reader :reserved2
# @see MachOStructure::FORMAT
FORMAT = "a16a16L=9".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 68
# system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00
# association of section flag symbols to values
# @api private
def initialize(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2)
@sectname = sectname
@segname = segname
@addr = addr
@size = size
@offset = offset
@align = align
@reloff = reloff
@nreloc = nreloc
@flags = flags
@reserved1 = reserved1
@reserved2 = reserved2
end
# @return [String] the section's name, with any trailing NULL characters removed
def section_name
sectname.delete("\x00")
end
# @return [String] the parent segment's name, with any trailing NULL characters removed
def segment_name
segname.delete("\x00")
end
# @return [Boolean] true if the section has no contents (i.e, `size` is 0)
def empty?
size.zero?
end
# @example
# puts "this section is regular" if sect.flag?(:S_REGULAR)
# @param flag [Symbol] a section flag symbol
# @return [Boolean] true if `flag` is present in the section's flag field
def flag?(flag)
flag = SECTION_FLAGS[flag]
return false if flag.nil?
flags & flag == flag
end
end
# Represents a section of a segment for 64-bit architectures.
class Section64 < Section
# @return [void] reserved
attr_reader :reserved3
# @see MachOStructure::FORMAT
FORMAT = "a16a16Q=2L=8".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 80
SECTION_FLAGS = {
:S_REGULAR => 0x0,
:S_ZEROFILL => 0x1,
:S_CSTRING_LITERALS => 0x2,
:S_4BYTE_LITERALS => 0x3,
:S_8BYTE_LITERALS => 0x4,
:S_LITERAL_POINTERS => 0x5,
:S_NON_LAZY_SYMBOL_POINTERS => 0x6,
:S_LAZY_SYMBOL_POINTERS => 0x7,
:S_SYMBOL_STUBS => 0x8,
:S_MOD_INIT_FUNC_POINTERS => 0x9,
:S_MOD_TERM_FUNC_POINTERS => 0xa,
:S_COALESCED => 0xb,
:S_GB_ZEROFILE => 0xc,
:S_INTERPOSING => 0xd,
:S_16BYTE_LITERALS => 0xe,
:S_DTRACE_DOF => 0xf,
:S_LAZY_DYLIB_SYMBOL_POINTERS => 0x10,
:S_THREAD_LOCAL_REGULAR => 0x11,
:S_THREAD_LOCAL_ZEROFILL => 0x12,
:S_THREAD_LOCAL_VARIABLES => 0x13,
:S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
:S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
:S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
:S_ATTR_NO_TOC => 0x40000000,
:S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
:S_ATTR_NO_DEAD_STRIP => 0x10000000,
:S_ATTR_LIVE_SUPPORT => 0x08000000,
:S_ATTR_SELF_MODIFYING_CODE => 0x04000000,
:S_ATTR_DEBUG => 0x02000000,
:S_ATTR_SOME_INSTRUCTIONS => 0x00000400,
:S_ATTR_EXT_RELOC => 0x00000200,
:S_ATTR_LOC_RELOC => 0x00000100,
}.freeze
# association of section name symbols to names
# @api private
def initialize(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2, reserved3)
super(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2)
@reserved3 = reserved3
SECTION_NAMES = {
:SECT_TEXT => "__text",
:SECT_FVMLIB_INIT0 => "__fvmlib_init0",
:SECT_FVMLIB_INIT1 => "__fvmlib_init1",
:SECT_DATA => "__data",
:SECT_BSS => "__bss",
:SECT_COMMON => "__common",
:SECT_OBJC_SYMBOLS => "__symbol_table",
:SECT_OBJC_MODULES => "__module_info",
:SECT_OBJC_STRINGS => "__selector_strs",
:SECT_OBJC_REFS => "__selector_refs",
:SECT_ICON_HEADER => "__header",
:SECT_ICON_TIFF => "__tiff",
}.freeze
# Represents a section of a segment for 32-bit architectures.
class Section < MachOStructure
# @return [String] the name of the section, including null pad bytes
attr_reader :sectname
# @return [String] the name of the segment's section, including null
# pad bytes
attr_reader :segname
# @return [Fixnum] the memory address of the section
attr_reader :addr
# @return [Fixnum] the size, in bytes, of the section
attr_reader :size
# @return [Fixnum] the file offset of the section
attr_reader :offset
# @return [Fixnum] the section alignment (power of 2) of the section
attr_reader :align
# @return [Fixnum] the file offset of the section's relocation entries
attr_reader :reloff
# @return [Fixnum] the number of relocation entries
attr_reader :nreloc
# @return [Fixnum] flags for type and attributes of the section
attr_reader :flags
# @return [void] reserved (for offset or index)
attr_reader :reserved1
# @return [void] reserved (for count or sizeof)
attr_reader :reserved2
# @see MachOStructure::FORMAT
FORMAT = "a16a16L=9".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 68
# @api private
def initialize(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2)
@sectname = sectname
@segname = segname
@addr = addr
@size = size
@offset = offset
@align = align
@reloff = reloff
@nreloc = nreloc
@flags = flags
@reserved1 = reserved1
@reserved2 = reserved2
end
# @return [String] the section's name, with any trailing NULL characters
# removed
def section_name
sectname.delete("\x00")
end
# @return [String] the parent segment's name, with any trailing NULL
# characters removed
def segment_name
segname.delete("\x00")
end
# @return [Boolean] whether the section is empty (i.e, {size} is 0)
def empty?
size.zero?
end
# @example
# puts "this section is regular" if sect.flag?(:S_REGULAR)
# @param flag [Symbol] a section flag symbol
# @return [Boolean] whether the flag is present in the section's {flags}
def flag?(flag)
flag = SECTION_FLAGS[flag]
return false if flag.nil?
flags & flag == flag
end
end
# Represents a section of a segment for 64-bit architectures.
class Section64 < Section
# @return [void] reserved
attr_reader :reserved3
# @see MachOStructure::FORMAT
FORMAT = "a16a16Q=2L=8".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 80
# @api private
def initialize(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2, reserved3)
super(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2)
@reserved3 = reserved3
end
end
end
end

View File

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

View File

@ -1,5 +1,6 @@
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
# @param filename [String] the Mach-O or Fat binary being read
# @return [Array<String>] an array of all dylibs linked to the binary
@ -9,7 +10,8 @@ module MachO
file.linked_dylibs
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 new_id [String] the new dylib ID for the binary
# @param options [Hash]
@ -23,7 +25,8 @@ module MachO
file.write!
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 old_name [String] the old shared library name
# @param new_name [String] the new shared library name
@ -38,7 +41,8 @@ module MachO
file.write!
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 old_path [String] the old runtime path
# @param new_path [String] the new runtime path
@ -67,7 +71,8 @@ module MachO
file.write!
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 old_path [String] the old runtime path
# @param options [Hash]
@ -80,5 +85,24 @@ module MachO
file.delete_rpath(old_path, options)
file.write!
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

View File

@ -5,7 +5,7 @@ module MachO
# @param value [Fixnum] the number being rounded
# @param round [Fixnum] the number being rounded with
# @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)
round -= 1
value += round
@ -13,7 +13,8 @@ module MachO
value
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 alignment [Fixnum] the number to alignment the size with
# @return [Fixnum] the number of pad bytes required
@ -21,7 +22,8 @@ module MachO
round(size, alignment) - size
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 endianness [Symbol] either `:big` or `:little`
# @return [String] the converted string
@ -31,7 +33,8 @@ module MachO
end
# 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 strings [Hash] the labeled strings to pack
# @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.
# @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)
MH_MAGICS.key?(num)
Headers::MH_MAGICS.key?(num)
end
# Compares the given number to valid Fat magic numbers.
# @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid Fat magic number, false otherwise
# @return [Boolean] whether `num` is a valid Fat magic number
def self.fat_magic?(num)
num == FAT_MAGIC
num == Headers::FAT_MAGIC
end
# Compares the given number to valid 32-bit Mach-O magic numbers.
# @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise
# @return [Boolean] whether `num` is a valid 32-bit magic number
def self.magic32?(num)
num == MH_MAGIC || num == MH_CIGAM
num == Headers::MH_MAGIC || num == Headers::MH_CIGAM
end
# Compares the given number to valid 64-bit Mach-O magic numbers.
# @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise
# @return [Boolean] whether `num` is a valid 64-bit magic number
def self.magic64?(num)
num == MH_MAGIC_64 || num == MH_CIGAM_64
num == Headers::MH_MAGIC_64 || num == Headers::MH_CIGAM_64
end
# Compares the given number to valid little-endian magic numbers.
# @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise
# @return [Boolean] whether `num` is a valid little-endian magic number
def self.little_magic?(num)
num == MH_CIGAM || num == MH_CIGAM_64
num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
end
# Compares the given number to valid big-endian magic numbers.
# @param num [Fixnum] the number being checked
# @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise
# @return [Boolean] whether `num` is a valid big-endian magic number
def self.big_magic?(num)
num == MH_CIGAM || num == MH_CIGAM_64
num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
end
end
end