Class: MachO::MachOFile
- Inherits:
-
Object
- Object
- MachO::MachOFile
- 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
-
#endianness ⇒ Symbol
readonly
The endianness of the file, :big or :little.
-
#filename ⇒ String
The filename loaded from, or nil if loaded from a binary string.
- #header ⇒ MachO::MachHeader, MachO::MachHeader64 readonly
-
#load_commands ⇒ Array<MachO::LoadCommand>
readonly
An array of the file's load commands.
Class Method Summary collapse
-
.new_from_bin(bin) ⇒ MachO::MachOFile
Creates a new MachOFile instance from a binary string.
Instance Method Summary collapse
-
#add_command(lc, options = {}) ⇒ void
Appends a new load command to the Mach-O.
-
#add_rpath(path, _options = {}) ⇒ void
Add the given runtime path to the Mach-O.
-
#alignment ⇒ Fixnum
The file's internal alignment.
-
#bundle? ⇒ Boolean
True if the file is of type
MH_BUNDLE, false otherwise. -
#change_dylib_id(new_id, _options = {}) ⇒ void
(also: #dylib_id=)
Changes the Mach-O's dylib ID to
new_id. -
#change_install_name(old_name, new_name, _options = {}) ⇒ void
(also: #change_dylib)
Changes the shared library
old_nametonew_name. -
#change_rpath(old_path, new_path, _options = {}) ⇒ void
Changes the runtime path
old_pathtonew_path. -
#command(name) ⇒ Array<MachO::LoadCommand>
(also: #[])
All load commands of a given name.
-
#core? ⇒ Boolean
True if the file is of type
MH_CORE, false otherwise. -
#cpusubtype ⇒ Symbol
A symbol representation of the Mach-O's CPU subtype.
-
#cputype ⇒ Symbol
A symbol representation of the Mach-O's CPU type.
-
#delete_command(lc, options = {}) ⇒ void
Delete a load command from the Mach-O.
-
#delete_rpath(path, _options = {}) ⇒ Object
Delete the given runtime path from the Mach-O.
-
#dsym? ⇒ Boolean
True if the file is of type
MH_DSYM, false otherwise. -
#dylib? ⇒ Boolean
True if the file is of type
MH_DYLIB, false otherwise. -
#dylib_id ⇒ String?
The Mach-O's dylib ID, or
nilif not a dylib. -
#dylib_load_commands ⇒ Array<MachO::DylibCommand>
All load commands responsible for loading dylibs.
-
#dylinker? ⇒ Boolean
True if the file is of type
MH_DYLINKER, false otherwise. -
#executable? ⇒ Boolean
True if the file is of type
MH_EXECUTE, false otherwise. -
#filetype ⇒ Symbol
A string representation of the Mach-O's filetype.
-
#flags ⇒ Fixnum
Execution flags set by the linker.
-
#fvmlib? ⇒ Boolean
True if the file is of type
MH_FVMLIB, false otherwise. -
#initialize(filename) ⇒ MachOFile
constructor
Creates a new FatFile from the given filename.
-
#initialize_from_bin(bin) ⇒ Object
private
Initializes a new MachOFile instance from a binary string.
-
#insert_command(offset, lc, options = {}) ⇒ Object
Inserts a load command at the given offset.
-
#kext? ⇒ Boolean
True if the file is of type
MH_KEXT_BUNDLE, false otherwise. -
#linked_dylibs ⇒ Array<String>
All shared libraries linked to the Mach-O.
-
#magic ⇒ Fixnum
The file's magic number.
-
#magic32? ⇒ Boolean
True if the Mach-O has 32-bit magic, false otherwise.
-
#magic64? ⇒ Boolean
True if the Mach-O has 64-bit magic, false otherwise.
-
#magic_string ⇒ String
A string representation of the file's magic number.
-
#ncmds ⇒ Fixnum
The number of load commands in the Mach-O's header.
-
#object? ⇒ Boolean
True if the file is of type
MH_OBJECT, false otherwise. -
#populate_fields ⇒ void
Populate the instance's fields with the raw Mach-O data.
-
#preload? ⇒ Boolean
True if the file is of type
MH_PRELOAD, false otherwise. -
#replace_command(old_lc, new_lc) ⇒ void
Replace a load command with another command in the Mach-O, preserving location.
-
#rpaths ⇒ Array<String>
All runtime paths searched by the dynamic linker for the Mach-O.
-
#sections(segment) ⇒ Array<MachO::Section>, Array<MachO::Section64>
deprecated
Deprecated.
use SegmentCommand#sections instead
-
#segments ⇒ Array<MachO::SegmentCommand>, Array<MachO::SegmentCommand64>
All segment load commands in the Mach-O.
-
#serialize ⇒ String
The file's raw Mach-O data.
-
#sizeofcmds ⇒ Fixnum
The size of all load commands, in bytes.
-
#write(filename) ⇒ void
Write all Mach-O data to the given filename.
-
#write! ⇒ void
Write all Mach-O data to the file used to initialize the instance.
Constructor Details
#initialize(filename) ⇒ MachOFile
Creates a new FatFile from the given filename.
35 36 37 38 39 40 41 |
# File 'lib/macho/macho_file.rb', line 35 def initialize(filename) raise ArgumentError, "#{filename}: no such file" unless File.file?(filename) @filename = filename @raw_data = File.open(@filename, "rb", &:read) populate_fields end |
Instance Attribute Details
#endianness ⇒ Symbol (readonly)
Returns the endianness of the file, :big or :little.
12 13 14 |
# File 'lib/macho/macho_file.rb', line 12 def endianness @endianness end |
#filename ⇒ String
Returns 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 |
#header ⇒ MachO::MachHeader, MachO::MachHeader64 (readonly)
16 17 18 |
# File 'lib/macho/macho_file.rb', line 16 def header @header end |
#load_commands ⇒ Array<MachO::LoadCommand> (readonly)
load commands are provided in order of ascending offset.
Returns an array of the file's load commands.
20 21 22 |
# File 'lib/macho/macho_file.rb', line 20 def load_commands @load_commands end |
Class Method Details
.new_from_bin(bin) ⇒ MachO::MachOFile
Creates a new MachOFile instance from a binary string.
25 26 27 28 29 30 |
# File 'lib/macho/macho_file.rb', line 25 def self.new_from_bin(bin) instance = allocate instance.initialize_from_bin(bin) instance end |
Instance Method Details
#add_command(lc, options = {}) ⇒ void
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
afterwards.
This method returns an undefined value.
Appends a new load command to the Mach-O.
239 240 241 |
# File 'lib/macho/macho_file.rb', line 239 def add_command(lc, = {}) insert_command(header.class.bytesize + sizeofcmds, lc, ) end |
#add_rpath(path, _options = {}) ⇒ void
_options is currently unused and is provided for signature
compatibility with FatFile#add_rpath
This method returns an undefined value.
Add the given runtime path to the Mach-O.
405 406 407 408 409 410 |
# File 'lib/macho/macho_file.rb', line 405 def add_rpath(path, = {}) raise RpathExistsError, path if rpaths.include?(path) rpath_cmd = LoadCommand.create(:LC_RPATH, path) add_command(rpath_cmd) end |
#alignment ⇒ Fixnum
Returns the file's internal alignment.
69 70 71 |
# File 'lib/macho/macho_file.rb', line 69 def alignment magic32? ? 4 : 8 end |
#bundle? ⇒ Boolean
Returns true if the file is of type MH_BUNDLE, false otherwise.
109 110 111 |
# File 'lib/macho/macho_file.rb', line 109 def bundle? header.filetype == MH_BUNDLE end |
#change_dylib_id(new_id, _options = {}) ⇒ void Also known as: dylib_id=
_options is currently unused and is provided for signature
compatibility with FatFile#change_dylib_id
This method returns an undefined value.
Changes the Mach-O's dylib ID to new_id. Does nothing if not a dylib.
315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/macho/macho_file.rb', line 315 def change_dylib_id(new_id, = {}) raise ArgumentError, "new ID must be a String" unless new_id.is_a?(String) return unless dylib? old_lc = command(:LC_ID_DYLIB).first raise DylibIdMissingError unless old_lc new_lc = LoadCommand.create(:LC_ID_DYLIB, new_id, old_lc., old_lc.current_version, old_lc.compatibility_version) replace_command(old_lc, new_lc) end |
#change_install_name(old_name, new_name, _options = {}) ⇒ void Also known as: change_dylib
_options is currently unused and is provided for signature
compatibility with FatFile#change_install_name
This method returns an undefined value.
Changes the shared library old_name to new_name
352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/macho/macho_file.rb', line 352 def change_install_name(old_name, new_name, = {}) 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., old_lc.current_version, old_lc.compatibility_version) replace_command(old_lc, new_lc) end |
#change_rpath(old_path, new_path, _options = {}) ⇒ void
_options is currently unused and is provided for signature
compatibility with FatFile#change_rpath
This method returns an undefined value.
Changes the runtime path old_path to new_path
383 384 385 386 387 388 389 390 391 392 |
# File 'lib/macho/macho_file.rb', line 383 def change_rpath(old_path, new_path, = {}) 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 = LoadCommand.create(:LC_RPATH, new_path) delete_rpath(old_path) insert_command(old_lc.view.offset, new_lc) end |
#command(name) ⇒ Array<MachO::LoadCommand> Also known as: []
All load commands of a given name.
169 170 171 |
# File 'lib/macho/macho_file.rb', line 169 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.
89 90 91 |
# File 'lib/macho/macho_file.rb', line 89 def core? header.filetype == MH_CORE end |
#cpusubtype ⇒ Symbol
Returns a symbol representation of the Mach-O's CPU subtype.
144 145 146 |
# File 'lib/macho/macho_file.rb', line 144 def cpusubtype CPU_SUBTYPES[header.cputype][header.cpusubtype] end |
#cputype ⇒ Symbol
Returns a symbol representation of the Mach-O's CPU type.
139 140 141 |
# File 'lib/macho/macho_file.rb', line 139 def cputype CPU_TYPES[header.cputype] end |
#delete_command(lc, options = {}) ⇒ void
This is public, but methods like #delete_rpath should be preferred.
Setting repopulate to false will leave the instance in an
inconsistent state unless #populate_fields is called immediately
afterwards.
This method returns an undefined value.
Delete a load command from the Mach-O.
253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/macho/macho_file.rb', line 253 def delete_command(lc, = {}) @raw_data.slice!(lc.view.offset, lc.cmdsize) # update Mach-O header fields to account for deleted load command update_ncmds(ncmds - 1) update_sizeofcmds(sizeofcmds - lc.cmdsize) # pad the space after the load commands to preserve offsets null_pad = "\x00" * lc.cmdsize @raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad) populate_fields if .fetch(:repopulate, true) end |
#delete_rpath(path, _options = {}) ⇒ Object
_options is currently unused and is provided for signature
compatibility with FatFile#delete_rpath
Delete the given runtime path from the Mach-O.
423 424 425 426 427 428 429 430 431 432 |
# File 'lib/macho/macho_file.rb', line 423 def delete_rpath(path, = {}) rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path } raise RpathUnknownError, path if rpath_cmds.empty? # delete the commands in reverse order, offset descending. this # allows us to defer (expensive) field population until the very end rpath_cmds.reverse_each { |cmd| delete_command(cmd, :repopulate => false) } populate_fields end |
#dsym? ⇒ Boolean
Returns true if the file is of type MH_DSYM, false otherwise.
114 115 116 |
# File 'lib/macho/macho_file.rb', line 114 def dsym? header.filetype == MH_DSYM end |
#dylib? ⇒ Boolean
Returns true if the file is of type MH_DYLIB, false otherwise.
99 100 101 |
# File 'lib/macho/macho_file.rb', line 99 def dylib? header.filetype == MH_DYLIB end |
#dylib_id ⇒ String?
The Mach-O's dylib ID, or nil if not a dylib.
298 299 300 301 302 303 304 |
# File 'lib/macho/macho_file.rb', line 298 def dylib_id return unless dylib? dylib_id_cmd = command(:LC_ID_DYLIB).first dylib_id_cmd.name.to_s end |
#dylib_load_commands ⇒ Array<MachO::DylibCommand>
All load commands responsible for loading dylibs.
279 280 281 |
# File 'lib/macho/macho_file.rb', line 279 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.
104 105 106 |
# File 'lib/macho/macho_file.rb', line 104 def dylinker? header.filetype == MH_DYLINKER end |
#executable? ⇒ Boolean
Returns true if the file is of type MH_EXECUTE, false otherwise.
79 80 81 |
# File 'lib/macho/macho_file.rb', line 79 def executable? header.filetype == MH_EXECUTE end |
#filetype ⇒ Symbol
Returns a string representation of the Mach-O's filetype.
134 135 136 |
# File 'lib/macho/macho_file.rb', line 134 def filetype MH_FILETYPES[header.filetype] end |
#flags ⇒ Fixnum
Returns execution flags set by the linker.
159 160 161 |
# File 'lib/macho/macho_file.rb', line 159 def flags header.flags end |
#fvmlib? ⇒ Boolean
Returns true if the file is of type MH_FVMLIB, false otherwise.
84 85 86 |
# File 'lib/macho/macho_file.rb', line 84 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.
Initializes a new MachOFile instance from a binary string.
46 47 48 49 50 |
# File 'lib/macho/macho_file.rb', line 46 def initialize_from_bin(bin) @filename = nil @raw_data = bin populate_fields end |
#insert_command(offset, lc, options = {}) ⇒ Object
Calling this method with an arbitrary offset in the load command region will leave the object in an inconsistent state.
Inserts a load command at the given offset.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/macho/macho_file.rb', line 185 def insert_command(offset, lc, = {}) context = LoadCommand::SerializationContext.context_for(self) cmd_raw = lc.serialize(context) if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff raise OffsetInsertionError, offset end new_sizeofcmds = sizeofcmds + cmd_raw.bytesize if header.class.bytesize + new_sizeofcmds > low_fileoff raise HeaderPadError, @filename end # update Mach-O header fields to account for inserted load command update_ncmds(ncmds + 1) update_sizeofcmds(new_sizeofcmds) @raw_data.insert(offset, cmd_raw) @raw_data.slice!(header.class.bytesize + new_sizeofcmds, cmd_raw.bytesize) populate_fields if .fetch(:repopulate, true) end |
#kext? ⇒ Boolean
Returns true if the file is of type MH_KEXT_BUNDLE, false otherwise.
119 120 121 |
# File 'lib/macho/macho_file.rb', line 119 def kext? header.filetype == MH_KEXT_BUNDLE end |
#linked_dylibs ⇒ Array<String>
All shared libraries linked to the Mach-O.
334 335 336 337 338 339 340 |
# File 'lib/macho/macho_file.rb', line 334 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 |
#magic ⇒ Fixnum
Returns the file's magic number.
124 125 126 |
# File 'lib/macho/macho_file.rb', line 124 def magic header.magic end |
#magic32? ⇒ Boolean
Returns true if the Mach-O has 32-bit magic, false otherwise.
59 60 61 |
# File 'lib/macho/macho_file.rb', line 59 def magic32? Utils.magic32?(header.magic) end |
#magic64? ⇒ Boolean
Returns true if the Mach-O has 64-bit magic, false otherwise.
64 65 66 |
# File 'lib/macho/macho_file.rb', line 64 def magic64? Utils.magic64?(header.magic) end |
#magic_string ⇒ String
Returns a string representation of the file's magic number.
129 130 131 |
# File 'lib/macho/macho_file.rb', line 129 def magic_string MH_MAGICS[magic] end |
#ncmds ⇒ Fixnum
Returns the number of load commands in the Mach-O's header.
149 150 151 |
# File 'lib/macho/macho_file.rb', line 149 def ncmds header.ncmds end |
#object? ⇒ Boolean
Returns true if the file is of type MH_OBJECT, false otherwise.
74 75 76 |
# File 'lib/macho/macho_file.rb', line 74 def object? header.filetype == MH_OBJECT end |
#populate_fields ⇒ void
This method is public, but should (almost) never need to be called.
The exception to this rule is when methods like #add_command and
#delete_command have been called with repopulate = false.
This method returns an undefined value.
Populate the instance's fields with the raw Mach-O data.
272 273 274 275 |
# File 'lib/macho/macho_file.rb', line 272 def populate_fields @header = populate_mach_header @load_commands = populate_load_commands end |
#preload? ⇒ Boolean
Returns true if the file is of type MH_PRELOAD, false otherwise.
94 95 96 |
# File 'lib/macho/macho_file.rb', line 94 def preload? header.filetype == MH_PRELOAD end |
#replace_command(old_lc, new_lc) ⇒ void
This is public, but methods like #dylib_id= should be preferred.
This method returns an undefined value.
Replace a load command with another command in the Mach-O, preserving location.
216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/macho/macho_file.rb', line 216 def replace_command(old_lc, new_lc) context = 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 raise HeaderPadError, @filename end delete_command(old_lc) insert_command(old_lc.view.offset, new_lc) end |
#rpaths ⇒ Array<String>
All runtime paths searched by the dynamic linker for the Mach-O.
368 369 370 |
# File 'lib/macho/macho_file.rb', line 368 def rpaths command(:LC_RPATH).map(&:path).map(&:to_s) end |
#sections(segment) ⇒ Array<MachO::Section>, Array<MachO::Section64>
use SegmentCommand#sections instead
All sections of the segment segment.
439 440 441 |
# File 'lib/macho/macho_file.rb', line 439 def sections(segment) segment.sections end |
#segments ⇒ Array<MachO::SegmentCommand>, Array<MachO::SegmentCommand64>
All segment load commands in the Mach-O.
286 287 288 289 290 291 292 |
# File 'lib/macho/macho_file.rb', line 286 def segments if magic32? command(:LC_SEGMENT) else command(:LC_SEGMENT_64) end end |
#serialize ⇒ String
The file's raw Mach-O data.
54 55 56 |
# File 'lib/macho/macho_file.rb', line 54 def serialize @raw_data end |
#sizeofcmds ⇒ Fixnum
Returns the size of all load commands, in bytes.
154 155 156 |
# File 'lib/macho/macho_file.rb', line 154 def sizeofcmds header.sizeofcmds end |
#write(filename) ⇒ void
This method returns an undefined value.
Write all Mach-O data to the given filename.
446 447 448 |
# File 'lib/macho/macho_file.rb', line 446 def write(filename) File.open(filename, "wb") { |f| f.write(@raw_data) } end |
#write! ⇒ void
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.
454 455 456 457 458 459 460 |
# File 'lib/macho/macho_file.rb', line 454 def write! if @filename.nil? raise MachOError, "cannot write to a default file when initialized from a binary string" else File.open(@filename, "wb") { |f| f.write(@raw_data) } end end |