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 filename does not exist



28
29
30
31
32
33
34
35
# File 'lib/macho/macho_file.rb', line 28

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

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

Instance Attribute Details

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

Returns:



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

def header
  @header
end

#load_commandsArray<MachO::LoadCommand> (readonly)

Returns an array of the file's load commands.

Returns:



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

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:



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

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

  instance
end

Instance Method Details

#bundle?Boolean

Returns true if the Mach-O is of type MH_BUNDLE, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O is of type MH_BUNDLE, false otherwise



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

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:



186
187
188
189
190
191
# File 'lib/macho/macho_file.rb', line 186

def change_install_name(old_name, new_name)
  dylib_cmd = command("LC_LOAD_DYLIB").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

#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"]

Returns:



121
122
123
# File 'lib/macho/macho_file.rb', line 121

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

#cpusubtypeString

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

Returns:

  • (String)

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



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

def cpusubtype
  CPU_SUBTYPES[header.cpusubtype]
end

#cputypeString

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

Returns:

  • (String)

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



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

def cputype
  CPU_TYPES[header.cputype]
end

#dylib?Boolean

Returns true if the Mach-O is of type MH_DYLIB, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O is of type MH_DYLIB, false otherwise



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

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



142
143
144
145
146
147
148
149
150
# File 'lib/macho/macho_file.rb', line 142

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



158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/macho/macho_file.rb', line 158

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

#executable?Boolean

Returns true if the Mach-O is of type MH_EXECUTE, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O is of type MH_EXECUTE, false otherwise



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

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



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

def filetype
  MH_FILETYPES[header.filetype]
end

#flagsFixnum

Returns execution flags set by the linker.

Returns:

  • (Fixnum)

    execution flags set by the linker



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

def flags
  header.flags
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.



38
39
40
41
42
43
# File 'lib/macho/macho_file.rb', line 38

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

#linked_dylibsArray<String>

All shared libraries linked to the Mach-O.

Returns:

  • (Array<String>)

    an array of all shared libraries



175
176
177
# File 'lib/macho/macho_file.rb', line 175

def linked_dylibs
  command("LC_LOAD_DYLIB").map(&:name).map(&:to_s)
end

#magicFixnum

Returns the Mach-O's magic number.

Returns:

  • (Fixnum)

    the Mach-O's magic number



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

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



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

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



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

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

#magic_stringString

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

Returns:

  • (String)

    a string representation of the Mach-O's magic number



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

def magic_string
  MH_MAGICS[header.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



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

def ncmds
  header.ncmds
end

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

All sections of the segment segment.

Parameters:

Returns:



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/macho/macho_file.rb', line 199

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(@raw_data.slice(offset, Section.bytesize))
      offset += Section.bytesize
    else
      sections << Section64.new_from_bin(@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:



130
131
132
133
134
135
136
# File 'lib/macho/macho_file.rb', line 130

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



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

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



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

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



228
229
230
# File 'lib/macho/macho_file.rb', line 228

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:

  • (MachOError)

    if the instance was created from a binary string

  • (MachO::MachOError)

    if the instance was initialized without a file



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

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