Merge pull request #12711 from Homebrew/dependabot/bundler/Library/Homebrew/ruby-macho-3.0.0

build(deps): bump ruby-macho from 2.5.1 to 3.0.0 in /Library/Homebrew
This commit is contained in:
Mike McQuaid 2022-01-13 10:36:08 +00:00 committed by GitHub
commit 4fc7e3d8a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 375 additions and 46 deletions

View File

@ -146,7 +146,7 @@ GEM
rubocop (~> 1.19)
rubocop-sorbet (0.6.5)
rubocop (>= 0.90.0)
ruby-macho (2.5.1)
ruby-macho (3.0.0)
ruby-progressbar (1.11.0)
rubyntlm (0.6.3)
simplecov (0.21.2)

View File

@ -1,9 +1,9 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `ruby-macho` gem.
# Please instead update this file by running `bin/tapioca gem ruby-macho`.
# typed: true
module MachO
class << self
def codesign!(filename); end
@ -20,6 +20,8 @@ class MachO::CPUTypeError < ::MachO::MachOError
end
class MachO::CodeSigningError < ::MachO::MachOError; end
class MachO::CompressedMachOError < ::MachO::MachOError; end
class MachO::DecompressionError < ::MachO::MachOError; end
class MachO::DylibIdMissingError < ::MachO::RecoverableModificationError
def initialize; end
@ -104,6 +106,9 @@ class MachO::HeaderPadError < ::MachO::ModificationError
end
module MachO::Headers; end
MachO::Headers::COMPRESSED_MAGIC = T.let(T.unsafe(nil), Integer)
MachO::Headers::COMP_TYPE_FASTLIB = T.let(T.unsafe(nil), Integer)
MachO::Headers::COMP_TYPE_LZSS = T.let(T.unsafe(nil), Integer)
MachO::Headers::CPU_ARCH_ABI32 = T.let(T.unsafe(nil), Integer)
MachO::Headers::CPU_ARCH_ABI64 = T.let(T.unsafe(nil), Integer)
MachO::Headers::CPU_SUBTYPES = T.let(T.unsafe(nil), Hash)
@ -220,6 +225,7 @@ MachO::Headers::MH_DYLIB = T.let(T.unsafe(nil), Integer)
MachO::Headers::MH_DYLIB_STUB = T.let(T.unsafe(nil), Integer)
MachO::Headers::MH_DYLINKER = T.let(T.unsafe(nil), Integer)
MachO::Headers::MH_EXECUTE = T.let(T.unsafe(nil), Integer)
MachO::Headers::MH_FILESET = T.let(T.unsafe(nil), Integer)
MachO::Headers::MH_FILETYPES = T.let(T.unsafe(nil), Hash)
MachO::Headers::MH_FLAGS = T.let(T.unsafe(nil), Hash)
MachO::Headers::MH_FVMLIB = T.let(T.unsafe(nil), Integer)
@ -242,6 +248,7 @@ class MachO::Headers::MachHeader < ::MachO::MachOStructure
def dylib?; end
def dylinker?; end
def executable?; end
def fileset?; end
def filetype; end
def flag?(flag); end
def flags; end
@ -269,6 +276,27 @@ MachO::Headers::MachHeader64::SIZEOF = T.let(T.unsafe(nil), Integer)
MachO::Headers::MachHeader::FORMAT = T.let(T.unsafe(nil), String)
MachO::Headers::MachHeader::SIZEOF = T.let(T.unsafe(nil), Integer)
class MachO::Headers::PrelinkedKernelHeader < ::MachO::MachOStructure
def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path); end
def adler32; end
def compress_type; end
def compressed_size; end
def kaslr?; end
def lzss?; end
def lzvn?; end
def platform_name; end
def prelink_version; end
def reserved; end
def root_path; end
def signature; end
def to_h; end
def uncompressed_size; end
end
MachO::Headers::PrelinkedKernelHeader::FORMAT = T.let(T.unsafe(nil), String)
MachO::Headers::PrelinkedKernelHeader::SIZEOF = T.let(T.unsafe(nil), Integer)
class MachO::JavaClassFileError < ::MachO::NotAMachOError
def initialize; end
end
@ -430,6 +458,19 @@ end
MachO::LoadCommands::EntryPointCommand::FORMAT = T.let(T.unsafe(nil), String)
MachO::LoadCommands::EntryPointCommand::SIZEOF = T.let(T.unsafe(nil), Integer)
class MachO::LoadCommands::FilesetEntryCommand < ::MachO::LoadCommands::LoadCommand
def initialize(view, cmd, cmdsize, vmaddr, fileoff, entry_id, reserved); end
def entry_id; end
def fileoff; end
def reserved; end
def to_h; end
def vmaddr; end
end
MachO::LoadCommands::FilesetEntryCommand::FORMAT = T.let(T.unsafe(nil), String)
MachO::LoadCommands::FilesetEntryCommand::SIZEOF = T.let(T.unsafe(nil), Integer)
class MachO::LoadCommands::FvmfileCommand < ::MachO::LoadCommands::LoadCommand
def initialize(view, cmd, cmdsize, name, header_addr); end
@ -762,13 +803,13 @@ class MachO::MachOFile
def change_dylib(old_name, new_name, _options = T.unsafe(nil)); end
def change_dylib_id(new_id, _options = T.unsafe(nil)); end
def change_install_name(old_name, new_name, _options = T.unsafe(nil)); end
def change_rpath(old_path, new_path, _options = T.unsafe(nil)); end
def change_rpath(old_path, new_path, options = T.unsafe(nil)); end
def command(name); end
def core?(*args, &block); end
def cpusubtype; end
def cputype; end
def delete_command(lc, options = T.unsafe(nil)); end
def delete_rpath(path, _options = T.unsafe(nil)); end
def delete_rpath(path, options = T.unsafe(nil)); end
def dsym?(*args, &block); end
def dylib?(*args, &block); end
def dylib_id; end
@ -812,10 +853,12 @@ class MachO::MachOFile
def check_cpusubtype(cputype, cpusubtype); end
def check_cputype(cputype); end
def check_filetype(filetype); end
def decompress_macho_lzvn; end
def low_fileoff; end
def populate_and_check_magic; end
def populate_load_commands; end
def populate_mach_header; end
def populate_prelinked_kernel_header; end
def update_ncmds(ncmds); end
def update_sizeofcmds(size); end
@ -872,18 +915,22 @@ end
module MachO::Sections; end
MachO::Sections::MAX_SECT_ALIGN = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES_SYS = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES_USR = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES = T.let(T.unsafe(nil), Hash)
MachO::Sections::SECTION_ATTRIBUTES_MASK = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES_SYS_MASK = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_ATTRIBUTES_USR_MASK = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_FLAGS = T.let(T.unsafe(nil), Hash)
MachO::Sections::SECTION_NAMES = T.let(T.unsafe(nil), Hash)
MachO::Sections::SECTION_TYPE = T.let(T.unsafe(nil), Integer)
MachO::Sections::SECTION_TYPES = T.let(T.unsafe(nil), Hash)
MachO::Sections::SECTION_TYPE_MASK = T.let(T.unsafe(nil), Integer)
class MachO::Sections::Section < ::MachO::MachOStructure
def initialize(sectname, segname, addr, size, offset, align, reloff, nreloc, flags, reserved1, reserved2); end
def addr; end
def align; end
def attribute?(attr_sym); end
def attributes; end
def empty?; end
def flag?(flag); end
def flags; end
@ -898,6 +945,8 @@ class MachO::Sections::Section < ::MachO::MachOStructure
def segname; end
def size; end
def to_h; end
def type; end
def type?(type_sym); end
end
class MachO::Sections::Section64 < ::MachO::Sections::Section
@ -935,6 +984,7 @@ end
module MachO::Utils
class << self
def big_magic?(num); end
def compressed_magic?(num); end
def fat_magic32?(num); end
def fat_magic64?(num); end
def fat_magic?(num); end

View File

@ -91,7 +91,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.13.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.6.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-3.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov_json_formatter-0.1.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.21.2/lib"

View File

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

View File

@ -84,7 +84,7 @@ module MachO
# @param cpusubtype [Integer] the CPU sub-type of the unknown pair
def initialize(cputype, cpusubtype)
super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
end
end
@ -120,7 +120,7 @@ module MachO
# @param actual_arity [Integer] 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}"
" got #{actual_arity}"
end
end
@ -137,7 +137,7 @@ module MachO
# @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"
" malformed string"
end
end
@ -154,8 +154,8 @@ module MachO
# @param filename [String] the filename
def initialize(filename)
super "Updated load commands do not fit in the header of " \
"#{filename}. #{filename} needs to be relinked, possibly with " \
"-headerpad or -headerpad_max_install_names"
"#{filename}. #{filename} needs to be relinked, possibly with " \
"-headerpad or -headerpad_max_install_names"
end
end
@ -207,4 +207,14 @@ module MachO
" Consider merging with `fat64: true`"
end
end
# Raised when attempting to parse a compressed Mach-O without explicitly
# requesting decompression.
class CompressedMachOError < MachOError
end
# Raised when attempting to decompress a compressed Mach-O without adequate
# dependencies, or on other decompression errors.
class DecompressionError < MachOError
end
end

View File

@ -55,7 +55,7 @@ module MachO
machos.each do |macho|
macho_offset = Utils.round(offset, 2**macho.segment_alignment)
raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > ((2**32) - 1)
macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
@ -96,7 +96,7 @@ module MachO
@filename = filename
@options = opts
@raw_data = File.open(@filename, "rb", &:read)
@raw_data = File.binread(@filename)
populate_fields
end
@ -238,6 +238,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail.
# @option options [Boolean] :uniq (false) for each slice: if true, change
# each rpath simultaneously.
# @return [void]
# @see MachOFile#change_rpath
def change_rpath(old_path, new_path, options = {})
@ -268,6 +270,9 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail.
# @option options [Boolean] :uniq (false) for each slice: if true, delete
# only the first runtime path that matches. if false, delete all duplicate
# paths that match.
# @return void
# @see MachOFile#delete_rpath
def delete_rpath(path, options = {})
@ -291,7 +296,7 @@ module MachO
# @param filename [String] the file to write to
# @return [void]
def write(filename)
File.open(filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(filename, @raw_data)
end
# Write all (fat) data to the file used to initialize the instance.
@ -301,7 +306,7 @@ module MachO
def write!
raise MachOError, "no initial file to write to" if filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(@filename, @raw_data)
end
# @return [Hash] a hash representation of this {FatFile}

View File

@ -37,6 +37,18 @@ module MachO
# @api private
MH_CIGAM_64 = 0xcffaedfe
# compressed mach-o magic
# @api private
COMPRESSED_MAGIC = 0x636f6d70 # "comp"
# a compressed mach-o slice, using LZSS for compression
# @api private
COMP_TYPE_LZSS = 0x6c7a7373 # "lzss"
# a compressed mach-o slice, using LZVN ("FastLib") for compression
# @api private
COMP_TYPE_FASTLIB = 0x6c7a766e # "lzvn"
# association of magic numbers to string representations
# @api private
MH_MAGICS = {
@ -433,6 +445,11 @@ module MachO
# @api private
MH_KEXT_BUNDLE = 0xb
# a set of Mach-Os, running in the same userspace, sharing a linkedit. The kext collection files are an example
# of this object type
# @api private
MH_FILESET = 0xc
# association of filetypes to Symbol representations
# @api private
MH_FILETYPES = {
@ -447,6 +464,7 @@ module MachO
MH_DYLIB_STUB => :dylib_stub,
MH_DSYM => :dsym,
MH_KEXT_BUNDLE => :kext_bundle,
MH_FILESET => :fileset,
}.freeze
# association of mach header flag symbols to values
@ -478,6 +496,9 @@ module MachO
:MH_HAS_TLV_DESCRIPTORS => 0x800000,
:MH_NO_HEAP_EXECUTION => 0x1000000,
:MH_APP_EXTENSION_SAFE => 0x02000000,
:MH_NLIST_OUTOFSYNC_WITH_DYLDINFO => 0x04000000,
:MH_SIM_SUPPORT => 0x08000000,
:MH_DYLIB_IN_CACHE => 0x80000000,
}.freeze
# Fat binary header structure
@ -724,6 +745,11 @@ module MachO
filetype == Headers::MH_KEXT_BUNDLE
end
# @return [Boolean] whether or not the file is of type `MH_FILESET`
def fileset?
filetype == Headers::MH_FILESET
end
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
def magic32?
Utils.magic32?(magic)
@ -785,5 +811,88 @@ module MachO
}.merge super
end
end
# Prelinked kernel/"kernelcache" header structure
class PrelinkedKernelHeader < MachOStructure
# @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
attr_reader :signature
# @return [Integer] the type of compression used
attr_reader :compress_type
# @return [Integer] a checksum for the uncompressed data
attr_reader :adler32
# @return [Integer] the size of the uncompressed data, in bytes
attr_reader :uncompressed_size
# @return [Integer] the size of the compressed data, in bytes
attr_reader :compressed_size
# @return [Integer] the version of the prelink format
attr_reader :prelink_version
# @return [void]
attr_reader :reserved
# @return [void]
attr_reader :platform_name
# @return [void]
attr_reader :root_path
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>6a40a64a256"
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 384
# @api private
def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path)
super()
@signature = signature
@compress_type = compress_type
@adler32 = adler32
@uncompressed_size = uncompressed_size
@compressed_size = compressed_size
@prelink_version = prelink_version
@reserved = reserved.unpack("L>10")
@platform_name = platform_name
@root_path = root_path
end
# @return [Boolean] whether this prelinked kernel supports KASLR
def kaslr?
prelink_version >= 1
end
# @return [Boolean] whether this prelinked kernel is compressed with LZSS
def lzss?
compress_type == COMP_TYPE_LZSS
end
# @return [Boolean] whether this prelinked kernel is compressed with LZVN
def lzvn?
compress_type == COMP_TYPE_FASTLIB
end
# @return [Hash] a hash representation of this {PrelinkedKernelHeader}
def to_h
{
"signature" => signature,
"compress_type" => compress_type,
"adler32" => adler32,
"uncompressed_size" => uncompressed_size,
"compressed_size" => compressed_size,
"prelink_version" => prelink_version,
"reserved" => reserved,
"platform_name" => platform_name,
"root_path" => root_path,
}.merge super
end
end
end
end

View File

@ -63,7 +63,8 @@ module MachO
0x31 => :LC_NOTE,
0x32 => :LC_BUILD_VERSION,
(0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
(0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
(0x34 | LC_REQ_DYLD) => :LC_DYLD_CHAINED_FIXUPS,
(0x35 | LC_REQ_DYLD) => :LC_FILESET_ENTRY,
}.freeze
# association of symbol representations to load command constants
@ -150,7 +151,8 @@ module MachO
:LC_NOTE => "NoteCommand",
:LC_BUILD_VERSION => "BuildVersionCommand",
:LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
:LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
:LC_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
:LC_FILESET_ENTRY => "FilesetEntryCommand",
}.freeze
# association of segment name symbols to names
@ -173,6 +175,7 @@ module MachO
:SG_FVMLIB => 0x2,
:SG_NORELOC => 0x4,
:SG_PROTECTED_VERSION_1 => 0x8,
:SG_READ_ONLY => 0x10,
}.freeze
# The top-level Mach-O load command structure.
@ -1794,5 +1797,48 @@ module MachO
}.merge super
end
end
# A load command containing a description of a Mach-O that is a constituent of a fileset.
# Each entry is further described by its own Mach header.
# Corresponds to LC_FILESET_ENTRY.
class FilesetEntryCommand < LoadCommand
# @return [Integer] the virtual memory address of the entry
attr_reader :vmaddr
# @return [Integer] the file offset of the entry
attr_reader :fileoff
# @return [LCStr] the entry's ID
attr_reader :entry_id
# @return [void]
attr_reader :reserved
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2Q=2L=2"
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 28
def initialize(view, cmd, cmdsize, vmaddr, fileoff, entry_id, reserved)
super(view, cmd, cmdsize)
@vmaddr = vmaddr
@fileoff = fileoff
@entry_id = LCStr.new(self, entry_id)
@reserved = reserved
end
# @return [Hash] a hash representation of this {FilesetEntryCommand}
def to_h
{
"vmaddr" => vmaddr,
"fileoff" => fileoff,
"entry_id" => entry_id,
"reserved" => reserved,
}.merge super
end
end
end
end

View File

@ -34,7 +34,11 @@ module MachO
# @param bin [String] a binary string containing raw Mach-O data
# @param opts [Hash] options to control the parser with
# @option opts [Boolean] :permissive whether to ignore unknown load commands
# @option opts [Boolean] :decompress whether to decompress, if capable
# @return [MachOFile] a new MachOFile
# @note The `:decompress` option relies on non-default dependencies. Compression
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
# virtually all normal uses.
def self.new_from_bin(bin, **opts)
instance = allocate
instance.initialize_from_bin(bin, opts)
@ -46,13 +50,17 @@ module MachO
# @param filename [String] the Mach-O file to load from
# @param opts [Hash] options to control the parser with
# @option opts [Boolean] :permissive whether to ignore unknown load commands
# @option opts [Boolean] :decompress whether to decompress, if capable
# @raise [ArgumentError] if the given file does not exist
# @note The `:decompress` option relies on non-default dependencies. Compression
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
# virtually all normal uses.
def initialize(filename, **opts)
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
@filename = filename
@options = opts
@raw_data = File.open(@filename, "rb", &:read)
@raw_data = File.binread(@filename)
populate_fields
end
@ -152,8 +160,8 @@ module MachO
# the instance fields
# @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**.
# @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 = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = lc.serialize(context)
@ -196,7 +204,7 @@ module MachO
# Appends a new load command to the Mach-O.
# @param lc [LoadCommands::LoadCommand] the load command being added
# @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate
# @option f [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields
# @return [void]
# @see #insert_command
@ -368,20 +376,20 @@ module MachO
# file.change_rpath("/usr/lib", "/usr/local/lib")
# @param old_path [String] the old runtime path
# @param new_path [String] the new runtime path
# @param _options [Hash]
# @param options [Hash]
# @option options [Boolean] :uniq (false) if true, change duplicate
# rpaths simultaneously.
# @return [void]
# @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 = {})
def change_rpath(old_path, new_path, options = {})
old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
raise RpathUnknownError, old_path if old_lc.nil?
raise RpathExistsError, new_path if rpaths.include?(new_path)
new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
delete_rpath(old_path)
delete_rpath(old_path, options)
insert_command(old_lc.view.offset, new_lc)
end
@ -409,13 +417,18 @@ module MachO
# file.delete_rpath("/lib")
# file.rpaths # => []
# @param path [String] the runtime path to delete
# @param _options [Hash]
# @param options [Hash]
# @option options [Boolean] :uniq (false) if true, also delete
# duplicates of the requested path. If false, delete the first
# instance (by offset) of the requested path.
# @return void
# @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 = {})
rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
def delete_rpath(path, options = {})
uniq = options.fetch(:uniq, false)
search_method = uniq ? :select : :find
# Cast rpath_cmds into an Array so we can handle the uniq and non-uniq cases the same way
rpath_cmds = Array(command(:LC_RPATH).method(search_method).call { |r| r.path.to_s == path })
raise RpathUnknownError, path if rpath_cmds.empty?
# delete the commands in reverse order, offset descending.
@ -426,7 +439,7 @@ module MachO
# @param filename [String] the file to write to
# @return [void]
def write(filename)
File.open(filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(filename, @raw_data)
end
# Write all Mach-O data to the file used to initialize the instance.
@ -436,7 +449,7 @@ module MachO
def write!
raise MachOError, "no initial file to write to" if @filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(@filename, @raw_data)
end
# @return [Hash] a hash representation of this {MachOFile}
@ -458,6 +471,9 @@ module MachO
# the smallest Mach-O header is 28 bytes
raise TruncatedFileError if @raw_data.size < 28
magic = @raw_data[0..3].unpack1("N")
populate_prelinked_kernel_header if Utils.compressed_magic?(magic)
magic = populate_and_check_magic
mh_klass = Utils.magic32?(magic) ? Headers::MachHeader : Headers::MachHeader64
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
@ -469,6 +485,48 @@ module MachO
mh
end
# Read a compressed Mach-O header and check its validity, as well as whether we're able
# to parse it.
# @return [void]
# @raise [CompressedMachOError] if we weren't asked to perform decompression
# @raise [DecompressionError] if decompression is impossible or fails
# @api private
def populate_prelinked_kernel_header
raise CompressedMachOError unless options.fetch(:decompress, false)
@plh = Headers::PrelinkedKernelHeader.new_from_bin :big, @raw_data[0, Headers::PrelinkedKernelHeader.bytesize]
raise DecompressionError, "unsupported compression type: LZSS" if @plh.lzss?
raise DecompressionError, "unknown compression type: 0x#{plh.compress_type.to_s 16}" unless @plh.lzvn?
decompress_macho_lzvn
end
# Attempt to decompress a Mach-O file from the data specified in a prelinked kernel header.
# @return [void]
# @raise [DecompressionError] if decompression is impossible or fails
# @api private
# @note This method rewrites the internal state of {MachOFile} to pretend as if it was never
# compressed to begin with, allowing all other APIs to transparently act on compressed Mach-Os.
def decompress_macho_lzvn
begin
require "lzfse"
rescue LoadError
raise DecompressionError, "LZVN required but the optional 'lzfse' gem is not installed"
end
# From this point onwards, the internal buffer of this MachOFile refers to the decompressed
# contents specified by the prelinked kernel header.
begin
@raw_data = LZFSE.lzvn_decompress @raw_data.slice(Headers::PrelinkedKernelHeader.bytesize, @plh.compressed_size)
# Sanity checks.
raise DecompressionError if @raw_data.size != @plh.uncompressed_size
# TODO: check the adler32 CRC in @plh
rescue LZFSE::DecodeError
raise DecompressionError, "LZVN decompression failed"
end
end
# Read just the file's magic number and check its validity.
# @return [Integer] the magic
# @raise [MagicError] if the magic is not valid Mach-O magic
@ -553,8 +611,8 @@ module MachO
segments.each do |seg|
seg.sections.each do |sect|
next if sect.empty?
next if sect.flag?(:S_ZEROFILL)
next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
next if sect.type?(:S_ZEROFILL)
next if sect.type?(:S_THREAD_LOCAL_ZEROFILL)
next unless sect.offset < offset
offset = sect.offset

View File

@ -4,24 +4,24 @@ module MachO
# Classes and constants for parsing sections in Mach-O binaries.
module Sections
# type mask
SECTION_TYPE = 0x000000ff
SECTION_TYPE_MASK = 0x000000ff
# attributes mask
SECTION_ATTRIBUTES = 0xffffff00
SECTION_ATTRIBUTES_MASK = 0xffffff00
# user settable attributes mask
SECTION_ATTRIBUTES_USR = 0xff000000
SECTION_ATTRIBUTES_USR_MASK = 0xff000000
# system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00
SECTION_ATTRIBUTES_SYS_MASK = 0x00ffff00
# maximum specifiable section alignment, as a power of 2
# @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
MAX_SECT_ALIGN = 15
# association of section flag symbols to values
# association of section type symbols to values
# @api private
SECTION_FLAGS = {
SECTION_TYPES = {
:S_REGULAR => 0x0,
:S_ZEROFILL => 0x1,
:S_CSTRING_LITERALS => 0x2,
@ -44,6 +44,12 @@ module MachO
:S_THREAD_LOCAL_VARIABLES => 0x13,
:S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
:S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
:S_INIT_FUNC_OFFSETS => 0x16,
}.freeze
# association of section attribute symbols to values
# @api private
SECTION_ATTRIBUTES = {
:S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
:S_ATTR_NO_TOC => 0x40000000,
:S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
@ -56,6 +62,13 @@ module MachO
:S_ATTR_LOC_RELOC => 0x00000100,
}.freeze
# association of section flag symbols to values
# @api private
SECTION_FLAGS = {
**SECTION_TYPES,
**SECTION_ATTRIBUTES,
}.freeze
# association of section name symbols to names
# @api private
SECTION_NAMES = {
@ -147,6 +160,33 @@ module MachO
size.zero?
end
# @return [Integer] the raw numeric type of this section
def type
flags & SECTION_TYPE_MASK
end
# @example
# puts "this section is regular" if sect.type?(:S_REGULAR)
# @param type_sym [Symbol] a section type symbol
# @return [Boolean] whether this section is of the given type
def type?(type_sym)
type == SECTION_TYPES[type_sym]
end
# @return [Integer] the raw numeric attributes of this section
def attributes
flags & SECTION_ATTRIBUTES_MASK
end
# @example
# puts "pure instructions" if sect.attribute?(:S_ATTR_PURE_INSTRUCTIONS)
# @param attr_sym [Symbol] a section attribute symbol
# @return [Boolean] whether this section is of the given type
def attribute?(attr_sym)
!!(attributes & SECTION_ATTRIBUTES[attr_sym])
end
# @deprecated Use {#type?} or {#attribute?} instead.
# @example
# puts "this section is regular" if sect.flag?(:S_REGULAR)
# @param flag [Symbol] a section flag symbol

View File

@ -51,6 +51,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) whether or not to fail loudly
# with an exception if the change cannot be performed
# @option options [Boolean] :uniq (false) whether or not to change duplicate
# rpaths simultaneously
# @return [void]
def self.change_rpath(filename, old_path, new_path, options = {})
file = MachO.open(filename)
@ -80,6 +82,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) whether or not to fail loudly
# with an exception if the change cannot be performed
# @option options [Boolean] :uniq (false) whether or not to delete duplicate
# rpaths simultaneously
# @return [void]
def self.delete_rpath(filename, old_path, options = {})
file = MachO.open(filename)

View File

@ -121,5 +121,12 @@ module MachO
def self.big_magic?(num)
[Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
end
# Compares the given number to the known magic number for a compressed Mach-O slice.
# @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid compressed header magic number
def self.compressed_magic?(num)
num == Headers::COMPRESSED_MAGIC
end
end
end