Class: MachO::FatFile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/macho/fat_file.rb

Overview

Represents a "Fat" file, which contains a header, a listing of available architectures, and one or more Mach-O binaries.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, **opts) ⇒ FatFile

Note:

see MachOFile#initialize for currently valid options

Creates a new FatFile from the given filename.

Parameters:

  • filename (String)

    the fat file to load from

  • opts (Hash)

    options to control the parser with

Raises:

  • (ArgumentError)

    if the given file does not exist



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/macho/fat_file.rb', line 94

def initialize(filename, **opts)
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)

  @filename = filename
  @options = opts
  File.open(@filename, "rb") do |file|
    @raw_data = file.read(Headers::FatHeader.bytesize)
    @raw_data ||= ""
    populate_fat_header
    @raw_data << file.read.to_s
  end
  populate_fields
end

Instance Attribute Details

#fat_archsArray<Headers::FatArch>, Array<Headers::FatArch64] an array of fat architectures (readonly)

Returns ArrayHeaders::FatArch, Array<Headers::FatArch64] an array of fat architectures.

Returns:



24
25
26
# File 'lib/macho/fat_file.rb', line 24

def fat_archs
  @fat_archs
end

#filenameString

Returns the filename loaded from, or nil if loaded from a binary string.

Returns:

  • (String)

    the filename loaded from, or nil if loaded from a binary string



14
15
16
# File 'lib/macho/fat_file.rb', line 14

def filename
  @filename
end

#headerHeaders::FatHeader (readonly)

Returns the file's header.

Returns:



21
22
23
# File 'lib/macho/fat_file.rb', line 21

def header
  @header
end

#machosArray<MachOFile> (readonly)

Returns an array of Mach-O binaries.

Returns:

  • (Array<MachOFile>)

    an array of Mach-O binaries



27
28
29
# File 'lib/macho/fat_file.rb', line 27

def machos
  @machos
end

#optionsHash (readonly)

Note:

Options specified in a MachO::FatFile trickle down into the internal MachOFiles.

Returns any parser options that the instance was created with.

Returns:

  • (Hash)

    any parser options that the instance was created with



18
19
20
# File 'lib/macho/fat_file.rb', line 18

def options
  @options
end

Class Method Details

.new_from_bin(bin, **opts) ⇒ FatFile

Note:

see MachOFile#initialize for currently valid options

Creates a new FatFile instance from a binary string.

Parameters:

  • bin (String)

    a binary string containing raw Mach-O data

  • opts (Hash)

    options to control the parser with

Returns:



82
83
84
85
86
87
# File 'lib/macho/fat_file.rb', line 82

def self.new_from_bin(bin, **opts)
  instance = allocate
  instance.initialize_from_bin(bin, opts)

  instance
end

.new_from_machos(*machos, fat64: false) ⇒ FatFile

Creates a new FatFile from the given (single-arch) Mach-Os

Parameters:

  • machos (Array<MachOFile>)

    the machos to combine

  • fat64 (Boolean) (defaults to: false)

    whether to use Headers::FatArch64s to represent each slice

Returns:

  • (FatFile)

    a new FatFile containing the give machos

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/macho/fat_file.rb', line 36

def self.new_from_machos(*machos, fat64: false)
  raise ArgumentError, "expected at least one Mach-O" if machos.empty?

  fa_klass, magic = if fat64
    [Headers::FatArch64, Headers::FAT_MAGIC_64]
  else
    [Headers::FatArch, Headers::FAT_MAGIC]
  end

  # put the smaller alignments further forwards in fat macho, so that we do less padding
  machos = machos.sort_by(&:segment_alignment)

  bin = +""

  bin << Headers::FatHeader.new(magic, machos.size).serialize
  offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)

  macho_pads = {}

  machos.each do |macho|
    macho_offset = Utils.round(offset, 2**macho.segment_alignment)

    raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > ((2**32) - 1)

    macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)

    bin << fa_klass.new(macho.header.cputype, macho.header.cpusubtype,
                        macho_offset, macho.serialize.bytesize,
                        macho.segment_alignment).serialize

    offset += (macho.serialize.bytesize + macho_pads[macho])
  end

  machos.each do |macho| # rubocop:disable Style/CombinableLoops
    bin << Utils.nullpad(macho_pads[macho])
    bin << macho.serialize
  end

  new_from_bin(bin)
end

Instance Method Details

#add_rpath(path, options = {}) ⇒ void

This method returns an undefined value.

Add the given runtime path to the file's Mach-Os.

Parameters:

  • path (String)

    the new runtime path

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :strict (Boolean) — default: true

    if true, fail if one slice fails. if false, fail only if all slices fail.

See Also:



265
266
267
268
269
270
271
# File 'lib/macho/fat_file.rb', line 265

def add_rpath(path, options = {})
  each_macho(options) do |macho|
    macho.add_rpath(path, options)
  end

  repopulate_raw_machos
end

#bundle?Boolean

Returns whether or not the file is of type MH_BUNDLE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_BUNDLE



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#change_dylib_id(new_id, options = {}) ⇒ void Also known as: dylib_id=

This method returns an undefined value.

Changes the file's dylib ID to new_id. If the file is not a dylib, does nothing.

Examples:

file.change_dylib_id('libFoo.dylib')

Parameters:

  • new_id (String)

    the new dylib ID

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :strict (Boolean) — default: true

    if true, fail if one slice fails. if false, fail only if all slices fail.

Raises:

  • (ArgumentError)

    if new_id is not a String

See Also:



187
188
189
190
191
192
193
194
195
196
# File 'lib/macho/fat_file.rb', line 187

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?)

  each_macho(options) do |macho|
    macho.change_dylib_id(new_id, options)
  end

  repopulate_raw_machos
end

#change_install_name(old_name, new_name, options = {}) ⇒ void Also known as: change_dylib

This method returns an undefined value.

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.

Examples:

file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')

Parameters:

  • old_name (String)

    the shared library name being changed

  • new_name (String)

    the new name

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :strict (Boolean) — default: true

    if true, fail if one slice fails. if false, fail only if all slices fail.

See Also:



222
223
224
225
226
227
228
# File 'lib/macho/fat_file.rb', line 222

def change_install_name(old_name, new_name, options = {})
  each_macho(options) do |macho|
    macho.change_install_name(old_name, new_name, options)
  end

  repopulate_raw_machos
end

#change_rpath(old_path, new_path, options = {}) ⇒ void

This method returns an undefined value.

Change the runtime path old_path to new_path in the file's Mach-Os.

Parameters:

  • old_path (String)

    the old runtime path

  • new_path (String)

    the new runtime path

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :strict (Boolean) — default: true

    if true, fail if one slice fails. if false, fail only if all slices fail.

  • :uniq (Boolean) — default: false

    for each slice: if true, change each rpath simultaneously.

See Also:



250
251
252
253
254
255
256
# File 'lib/macho/fat_file.rb', line 250

def change_rpath(old_path, new_path, options = {})
  each_macho(options) do |macho|
    macho.change_rpath(old_path, new_path, options)
  end

  repopulate_raw_machos
end

#codesign!(identifier: nil) ⇒ void

This method returns an undefined value.

Replaces every embedded signature with a pure-Ruby ad-hoc signature.

Parameters:

  • identifier (String, nil) (defaults to: nil)

    the signing identifier



294
295
296
297
298
299
# File 'lib/macho/fat_file.rb', line 294

def codesign!(identifier: nil)
  identifier ||= CodeSigning.identifier(canonical_macho, filename)
  machos.each { |macho| macho.codesign!(:identifier => identifier) }
  repopulate_resized_raw_machos
  nil
end

#core?Boolean

Returns whether or not the file is of type MH_CORE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_CORE



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#delete_rpath(path, options = {}) ⇒ Object

Delete the given runtime path from the file's Mach-Os.

Parameters:

  • path (String)

    the runtime path to delete

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :strict (Boolean) — default: true

    if true, fail if one slice fails. if false, fail only if all slices fail.

  • :uniq (Boolean) — default: false

    for each slice: if true, delete only the first runtime path that matches. if false, delete all duplicate paths that match.

Returns:

  • void

See Also:



283
284
285
286
287
288
289
# File 'lib/macho/fat_file.rb', line 283

def delete_rpath(path, options = {})
  each_macho(options) do |macho|
    macho.delete_rpath(path, options)
  end

  repopulate_raw_machos
end

#dsym?Boolean

Returns whether or not the file is of type MH_DSYM.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DSYM



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#dylib?Boolean

Returns whether or not the file is of type MH_DYLIB.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DYLIB



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#dylib_idString?

Returns the Mach-O's dylib ID.

Returns:

  • (String, nil)

    the Mach-O's dylib ID



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#dylib_load_commandsArray<LoadCommands::DylibCommand>

All load commands responsible for loading dylibs in the file's Mach-O's.

Returns:



172
173
174
# File 'lib/macho/fat_file.rb', line 172

def dylib_load_commands
  machos.flat_map(&:dylib_load_commands)
end

#dylinker?Boolean

Returns whether or not the file is of type MH_DYLINKER.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DYLINKER



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#executable?Boolean

Returns whether or not the file is of type MH_EXECUTE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_EXECUTE



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#extract(cputype) ⇒ MachOFile?

Extract a Mach-O with the given CPU type from the file.

Examples:

file.extract(:i386) # => MachO::MachOFile

Parameters:

  • cputype (Symbol)

    the CPU type of the Mach-O being extracted

Returns:

  • (MachOFile, nil)

    the extracted Mach-O or nil if no Mach-O has the given CPU type



306
307
308
# File 'lib/macho/fat_file.rb', line 306

def extract(cputype)
  machos.select { |macho| macho.cputype == cputype }.first
end

#filetypeSymbol

Returns a string representation of the Mach-O's filetype.

Returns:

  • (Symbol)

    a string representation of the Mach-O's filetype



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#fvmlib?Boolean

Returns whether or not the file is of type MH_FVMLIB.

Returns:

  • (Boolean)

    whether or not the file is of type MH_FVMLIB



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#initialize_from_bin(bin, opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Initializes a new FatFile instance from a binary string with the given options.

See Also:



111
112
113
114
115
116
# File 'lib/macho/fat_file.rb', line 111

def initialize_from_bin(bin, opts)
  @filename = nil
  @options = opts
  @raw_data = bin
  populate_fields
end

#kext?Boolean

Returns whether or not the file is of type MH_KEXT_BUNDLE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_KEXT_BUNDLE



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#linked_dylibsArray<String>

All shared libraries linked to the file's Mach-Os.

Returns:

  • (Array<String>)

    an array of all shared libraries

See Also:



203
204
205
206
207
208
# File 'lib/macho/fat_file.rb', line 203

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.
  # the union of all libraries used by all architectures.
  machos.flat_map(&:linked_dylibs).uniq
end

#magicInteger

Returns the magic number of the header (and file).

Returns:

  • (Integer)

    the magic number of the header (and file)



154
# File 'lib/macho/fat_file.rb', line 154

def_delegators :header, :magic

#magic_stringString

Returns a string representation of the file's magic number.

Returns:

  • (String)

    a string representation of the file's magic number



157
158
159
# File 'lib/macho/fat_file.rb', line 157

def magic_string
  Headers::MH_MAGICS[magic]
end

#object?Boolean

Returns whether or not the file is of type MH_OBJECT.

Returns:

  • (Boolean)

    whether or not the file is of type MH_OBJECT



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#populate_fieldsvoid

Note:

This method is public, but should (almost) never need to be called.

This method returns an undefined value.

Populate the instance's fields with the raw Fat Mach-O data.



164
165
166
167
168
# File 'lib/macho/fat_file.rb', line 164

def populate_fields
  @header = populate_fat_header
  @fat_archs = populate_fat_archs
  @machos = populate_machos
end

#preload?Boolean

Returns whether or not the file is of type MH_PRELOAD.

Returns:

  • (Boolean)

    whether or not the file is of type MH_PRELOAD



148
149
150
# File 'lib/macho/fat_file.rb', line 148

def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
:core?, :preload?, :dylib?, :dylinker?, :bundle?,
:dsym?, :kext?, :filetype, :dylib_id

#rpathsArray<String>

All runtime paths associated with the file's Mach-Os.

Returns:

  • (Array<String>)

    an array of all runtime paths

See Also:



235
236
237
238
# File 'lib/macho/fat_file.rb', line 235

def rpaths
  # Can individual architectures have different runtime paths?
  machos.flat_map(&:rpaths).uniq
end

#serializeString

The file's raw fat data.

Returns:

  • (String)

    the raw fat data



120
121
122
# File 'lib/macho/fat_file.rb', line 120

def serialize
  @raw_data
end

#to_hHash

Returns a hash representation of this MachO::FatFile.

Returns:



328
329
330
331
332
333
334
# File 'lib/macho/fat_file.rb', line 328

def to_h
  {
    "header" => header.to_h,
    "fat_archs" => fat_archs.map(&:to_h),
    "machos" => machos.map(&:to_h),
  }
end

#write(filename) ⇒ void

This method returns an undefined value.

Write all (fat) data to the given filename.

Parameters:

  • filename (String)

    the file to write to



313
314
315
# File 'lib/macho/fat_file.rb', line 313

def write(filename)
  File.binwrite(filename, @raw_data)
end

#write!void

Note:

Overwrites all data in the file!

This method returns an undefined value.

Write all (fat) data to the file used to initialize the instance.

Raises:

  • (MachOError)

    if the instance was initialized without a file



321
322
323
324
325
# File 'lib/macho/fat_file.rb', line 321

def write!
  raise MachOError, "no initial file to write to" if filename.nil?

  File.binwrite(@filename, @raw_data)
end