Class: MachO::MachOFile

Inherits:
Object
  • Object
show all
Defined in:
lib/macho/macho_file.rb

Overview

Represents a Mach-O file, which contains a header and load commands as well as binary executable instructions. Mach-O binaries are architecture specific.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ MachOFile

Creates a new FatFile from the given filename.

Parameters:

  • filename (String)

    the Mach-O file to load from

Raises:

  • (ArgumentError)

    if the given file does not exist



34
35
36
37
38
39
40
41
# File 'lib/macho/macho_file.rb', line 34

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

  @filename = filename
  @raw_data = File.open(@filename, "rb") { |f| f.read }
  @header = get_mach_header
  @load_commands = get_load_commands
end

Instance Attribute Details

#endiannessSymbol (readonly)

Returns the endianness of the file, :big or :little.

Returns:

  • (Symbol)

    the endianness of the file, :big or :little



12
13
14
# File 'lib/macho/macho_file.rb', line 12

def endianness
  @endianness
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



9
10
11
# File 'lib/macho/macho_file.rb', line 9

def filename
  @filename
end

#headerMachO::MachHeader, MachO::MachHeader64 (readonly)

Returns:



16
17
18
# File 'lib/macho/macho_file.rb', line 16

def header
  @header
end

#load_commandsArray<MachO::LoadCommand> (readonly)

Returns an array of the file's load commands.

Returns:



19
20
21
# File 'lib/macho/macho_file.rb', line 19

def load_commands
  @load_commands
end

Class Method Details

.new_from_bin(bin) ⇒ MachO::MachOFile

Creates a new MachOFile instance from a binary string.

Parameters:

  • bin (String)

    a binary string containing raw Mach-O data

Returns:



24
25
26
27
28
29
# File 'lib/macho/macho_file.rb', line 24

def self.new_from_bin(bin)
  instance = allocate
  instance.initialize_from_bin(bin)

  instance
end

Instance Method Details

#bundle?Boolean

Returns true if the file is of type MH_BUNDLE, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_BUNDLE, false otherwise



103
104
105
# File 'lib/macho/macho_file.rb', line 103

def bundle?
  header.filetype == MH_BUNDLE
end

#change_install_name(old_name, new_name) ⇒ void Also known as: change_dylib

This method returns an undefined value.

Changes the shared library old_name to new_name

Examples:

file.change_install_name("/usr/lib/libWhatever.dylib", "/usr/local/lib/libWhatever2.dylib")

Parameters:

  • old_name (String)

    the shared library's old name

  • new_name (String)

    the shared library's new name

Raises:



238
239
240
241
242
243
# File 'lib/macho/macho_file.rb', line 238

def change_install_name(old_name, new_name)
  dylib_cmd = dylib_load_commands.find { |d| d.name.to_s == old_name }
  raise DylibUnknownError.new(old_name) if dylib_cmd.nil?

  set_name_in_dylib(dylib_cmd, old_name, new_name)
end

#change_rpath(old_path, new_path) ⇒ void

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.

This method returns an undefined value.

Changes the runtime path old_path to new_path

Examples:

file.change_rpath("/usr/lib", "/usr/local/lib")

Parameters:

  • old_path (String)

    the old runtime path

  • new_path (String)

    the new runtime path

Raises:



261
262
263
264
265
266
# File 'lib/macho/macho_file.rb', line 261

def change_rpath(old_path, new_path)
  rpath_cmd = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
  raise RpathUnknownError.new(old_path) if rpath_cmd.nil?

  set_path_in_rpath(rpath_cmd, old_path, new_path)
end

#command(name) ⇒ Array<MachO::LoadCommand> Also known as: []

All load commands of a given name.

Examples:

file.command("LC_LOAD_DYLIB")
file[:LC_LOAD_DYLIB]

Parameters:

  • name (String, Symbol)

    the load command ID

Returns:



163
164
165
# File 'lib/macho/macho_file.rb', line 163

def command(name)
  load_commands.select { |lc| lc.type == name.to_sym }
end

#core?Boolean

Returns true if the file is of type MH_CORE, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_CORE, false otherwise



83
84
85
# File 'lib/macho/macho_file.rb', line 83

def core?
  header.filetype == MH_CORE
end

#cpusubtypeSymbol

Returns a symbol representation of the Mach-O's CPU subtype.

Returns:

  • (Symbol)

    a symbol representation of the Mach-O's CPU subtype



138
139
140
# File 'lib/macho/macho_file.rb', line 138

def cpusubtype
  CPU_SUBTYPES[header.cputype][header.cpusubtype]
end

#cputypeSymbol

Returns a symbol representation of the Mach-O's CPU type.

Returns:

  • (Symbol)

    a symbol representation of the Mach-O's CPU type



133
134
135
# File 'lib/macho/macho_file.rb', line 133

def cputype
  CPU_TYPES[header.cputype]
end

#dsym?Boolean

Returns true if the file is of type MH_DSYM, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_DSYM, false otherwise



108
109
110
# File 'lib/macho/macho_file.rb', line 108

def dsym?
  header.filetype == MH_DSYM
end

#dylib?Boolean

Returns true if the file is of type MH_DYLIB, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_DYLIB, false otherwise



93
94
95
# File 'lib/macho/macho_file.rb', line 93

def dylib?
  header.filetype == MH_DYLIB
end

#dylib_idString?

The Mach-O's dylib ID, or nil if not a dylib.

Examples:

file.dylib_id # => 'libBar.dylib'

Returns:

  • (String, nil)

    the Mach-O's dylib ID



190
191
192
193
194
195
196
197
198
# File 'lib/macho/macho_file.rb', line 190

def dylib_id
  if !dylib?
    return nil
  end

  dylib_id_cmd = command(:LC_ID_DYLIB).first

  dylib_id_cmd.name.to_s
end

#dylib_id=(new_id) ⇒ void

This method returns an undefined value.

Changes the Mach-O's dylib ID to new_id. Does nothing if not a dylib.

Examples:

file.dylib_id = "libFoo.dylib"

Parameters:

  • new_id (String)

    the dylib's new ID

Raises:

  • (ArgumentError)

    if new_id is not a String



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/macho/macho_file.rb', line 206

def dylib_id=(new_id)
  if !new_id.is_a?(String)
    raise ArgumentError.new("argument must be a String")
  end

  if !dylib?
    return nil
  end

  dylib_cmd = command(:LC_ID_DYLIB).first
  old_id = dylib_id

  set_name_in_dylib(dylib_cmd, old_id, new_id)
end

#dylib_load_commandsArray<MachO::DylibCommand>

All load commands responsible for loading dylibs.

Returns:



171
172
173
# File 'lib/macho/macho_file.rb', line 171

def dylib_load_commands
  load_commands.select { |lc| DYLIB_LOAD_COMMANDS.include?(lc.type) }
end

#dylinker?Boolean

Returns true if the file is of type MH_DYLINKER, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_DYLINKER, false otherwise



98
99
100
# File 'lib/macho/macho_file.rb', line 98

def dylinker?
  header.filetype == MH_DYLINKER
end

#executable?Boolean

Returns true if the file is of type MH_EXECUTE, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_EXECUTE, false otherwise



73
74
75
# File 'lib/macho/macho_file.rb', line 73

def executable?
  header.filetype == MH_EXECUTE
end

#filetypeString

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

Returns:

  • (String)

    a string representation of the Mach-O's filetype



128
129
130
# File 'lib/macho/macho_file.rb', line 128

def filetype
  MH_FILETYPES[header.filetype]
end

#flagsFixnum

Returns execution flags set by the linker.

Returns:

  • (Fixnum)

    execution flags set by the linker



153
154
155
# File 'lib/macho/macho_file.rb', line 153

def flags
  header.flags
end

#fvmlib?Boolean

Returns true if the file is of type MH_FVMLIB, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_FVMLIB, false otherwise



78
79
80
# File 'lib/macho/macho_file.rb', line 78

def fvmlib?
  header.filetype == MH_FVMLIB
end

#initialize_from_bin(bin) ⇒ 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.



44
45
46
47
48
49
# File 'lib/macho/macho_file.rb', line 44

def initialize_from_bin(bin)
  @filename = nil
  @raw_data = bin
  @header = get_mach_header
  @load_commands = get_load_commands
end

#kext?Boolean

Returns true if the file is of type MH_KEXT_BUNDLE, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_KEXT_BUNDLE, false otherwise



113
114
115
# File 'lib/macho/macho_file.rb', line 113

def kext?
  header.filetype == MH_KEXT_BUNDLE
end

#linked_dylibsArray<String>

All shared libraries linked to the Mach-O.

Returns:

  • (Array<String>)

    an array of all shared libraries



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

def linked_dylibs
  # Some linkers produce multiple `LC_LOAD_DYLIB` load commands for the same
  # library, but at this point we're really only interested in a list of
  # unique libraries this Mach-O file links to, thus: `uniq`. (This is also
  # for consistency with `FatFile` that merges this list across all archs.)
  dylib_load_commands.map(&:name).map(&:to_s).uniq
end

#magicFixnum

Returns the file's magic number.

Returns:

  • (Fixnum)

    the file's magic number



118
119
120
# File 'lib/macho/macho_file.rb', line 118

def magic
  header.magic
end

#magic32?Boolean

Returns true if the Mach-O has 32-bit magic, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O has 32-bit magic, false otherwise



58
59
60
# File 'lib/macho/macho_file.rb', line 58

def magic32?
  MachO.magic32?(header.magic)
end

#magic64?Boolean

Returns true if the Mach-O has 64-bit magic, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O has 64-bit magic, false otherwise



63
64
65
# File 'lib/macho/macho_file.rb', line 63

def magic64?
  MachO.magic64?(header.magic)
end

#magic_stringString

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

Returns:

  • (String)

    a string representation of the file's magic number



123
124
125
# File 'lib/macho/macho_file.rb', line 123

def magic_string
  MH_MAGICS[magic]
end

#ncmdsFixnum

Returns the number of load commands in the Mach-O's header.

Returns:

  • (Fixnum)

    the number of load commands in the Mach-O's header



143
144
145
# File 'lib/macho/macho_file.rb', line 143

def ncmds
  header.ncmds
end

#object?Boolean

Returns true if the file is of type MH_OBJECT, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_OBJECT, false otherwise



68
69
70
# File 'lib/macho/macho_file.rb', line 68

def object?
  header.filetype == MH_OBJECT
end

#preload?Boolean

Returns true if the file is of type MH_PRELOAD, false otherwise.

Returns:

  • (Boolean)

    true if the file is of type MH_PRELOAD, false otherwise



88
89
90
# File 'lib/macho/macho_file.rb', line 88

def preload?
  header.filetype == MH_PRELOAD
end

#rpathsArray<String>

All runtime paths searched by the dynamic linker for the Mach-O.

Returns:

  • (Array<String>)

    an array of all runtime paths



249
250
251
# File 'lib/macho/macho_file.rb', line 249

def rpaths
  command(:LC_RPATH).map(&:path).map(&:to_s)
end

#sections(segment) ⇒ Array<MachO::Section>, Array<MachO::Section64>

All sections of the segment segment.

Parameters:

Returns:



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/macho/macho_file.rb', line 272

def sections(segment)
  sections = []

  if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
    raise ArgumentError.new("not a valid segment")
  end

  if segment.nsects.zero?
    return sections
  end

  offset = segment.offset + segment.class.bytesize

  segment.nsects.times do
    if segment.is_a? SegmentCommand
      sections << Section.new_from_bin(endianness, @raw_data.slice(offset, Section.bytesize))
      offset += Section.bytesize
    else
      sections << Section64.new_from_bin(endianness, @raw_data.slice(offset, Section64.bytesize))
      offset += Section64.bytesize
    end
  end

  sections
end

#segmentsArray<MachO::SegmentCommand>, Array<MachO::SegmentCommand64>

All segment load commands in the Mach-O.

Returns:



178
179
180
181
182
183
184
# File 'lib/macho/macho_file.rb', line 178

def segments
  if magic32?
    command(:LC_SEGMENT)
  else
    command(:LC_SEGMENT_64)
  end
end

#serializeString

The file's raw Mach-O data.

Returns:

  • (String)

    the raw Mach-O data



53
54
55
# File 'lib/macho/macho_file.rb', line 53

def serialize
  @raw_data
end

#sizeofcmdsFixnum

Returns the size of all load commands, in bytes.

Returns:

  • (Fixnum)

    the size of all load commands, in bytes



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

def sizeofcmds
  header.sizeofcmds
end

#write(filename) ⇒ void

This method returns an undefined value.

Write all Mach-O data to the given filename.

Parameters:

  • filename (String)

    the file to write to



301
302
303
# File 'lib/macho/macho_file.rb', line 301

def write(filename)
  File.open(filename, "wb") { |f| f.write(@raw_data) }
end

#write!void

Note:

Overwrites all data in the file!

This method returns an undefined value.

Write all Mach-O data to the file used to initialize the instance.

Raises:



309
310
311
312
313
314
315
# File 'lib/macho/macho_file.rb', line 309

def write!
  if @filename.nil?
    raise MachOError.new("cannot write to a default file when initialized from a binary string")
  else
    File.open(@filename, "wb") { |f| f.write(@raw_data) }
  end
end