ruby-macho 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -9
- data/lib/macho.rb +2 -1
- data/lib/macho/exceptions.rb +83 -6
- data/lib/macho/fat_file.rb +130 -39
- data/lib/macho/headers.rb +131 -25
- data/lib/macho/load_commands.rb +518 -177
- data/lib/macho/macho_file.rb +254 -172
- data/lib/macho/open.rb +5 -5
- data/lib/macho/sections.rb +20 -9
- data/lib/macho/structure.rb +8 -16
- data/lib/macho/tools.rb +34 -15
- data/lib/macho/utils.rb +87 -39
- data/lib/macho/view.rb +23 -0
- metadata +4 -2
data/lib/macho/macho_file.rb
CHANGED
@@ -16,6 +16,7 @@ module MachO
|
|
16
16
|
attr_reader :header
|
17
17
|
|
18
18
|
# @return [Array<MachO::LoadCommand>] an array of the file's load commands
|
19
|
+
# @note load commands are provided in order of ascending offset.
|
19
20
|
attr_reader :load_commands
|
20
21
|
|
21
22
|
# Creates a new MachOFile instance from a binary string.
|
@@ -32,20 +33,20 @@ module MachO
|
|
32
33
|
# @param filename [String] the Mach-O file to load from
|
33
34
|
# @raise [ArgumentError] if the given file does not exist
|
34
35
|
def initialize(filename)
|
35
|
-
raise ArgumentError
|
36
|
+
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
36
37
|
|
37
38
|
@filename = filename
|
38
|
-
@raw_data = File.open(@filename, "rb"
|
39
|
-
|
40
|
-
@load_commands = get_load_commands
|
39
|
+
@raw_data = File.open(@filename, "rb", &:read)
|
40
|
+
populate_fields
|
41
41
|
end
|
42
42
|
|
43
|
+
# Initializes a new MachOFile instance from a binary string.
|
44
|
+
# @see MachO::MachOFile.new_from_bin
|
43
45
|
# @api private
|
44
46
|
def initialize_from_bin(bin)
|
45
47
|
@filename = nil
|
46
48
|
@raw_data = bin
|
47
|
-
|
48
|
-
@load_commands = get_load_commands
|
49
|
+
populate_fields
|
49
50
|
end
|
50
51
|
|
51
52
|
# The file's raw Mach-O data.
|
@@ -56,12 +57,17 @@ module MachO
|
|
56
57
|
|
57
58
|
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
|
58
59
|
def magic32?
|
59
|
-
|
60
|
+
Utils.magic32?(header.magic)
|
60
61
|
end
|
61
62
|
|
62
63
|
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
|
63
64
|
def magic64?
|
64
|
-
|
65
|
+
Utils.magic64?(header.magic)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Fixnum] the file's internal alignment
|
69
|
+
def alignment
|
70
|
+
magic32? ? 4 : 8
|
65
71
|
end
|
66
72
|
|
67
73
|
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
|
@@ -124,7 +130,7 @@ module MachO
|
|
124
130
|
MH_MAGICS[magic]
|
125
131
|
end
|
126
132
|
|
127
|
-
# @return [
|
133
|
+
# @return [Symbol] a string representation of the Mach-O's filetype
|
128
134
|
def filetype
|
129
135
|
MH_FILETYPES[header.filetype]
|
130
136
|
end
|
@@ -164,7 +170,109 @@ module MachO
|
|
164
170
|
load_commands.select { |lc| lc.type == name.to_sym }
|
165
171
|
end
|
166
172
|
|
167
|
-
alias
|
173
|
+
alias [] command
|
174
|
+
|
175
|
+
# Inserts a load command at the given offset.
|
176
|
+
# @param offset [Fixnum] the offset to insert at
|
177
|
+
# @param lc [MachO::LoadCommand] the load command to insert
|
178
|
+
# @param options [Hash]
|
179
|
+
# @option options [Boolean] :repopulate (true) whether or not to repopulate
|
180
|
+
# the instance fields
|
181
|
+
# @raise [MachO::OffsetInsertionError] if the offset is not in the load command region
|
182
|
+
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer
|
183
|
+
# @note Calling this method with an arbitrary offset in the load command
|
184
|
+
# region **will leave the object in an inconsistent state**.
|
185
|
+
def insert_command(offset, lc, options = {})
|
186
|
+
context = LoadCommand::SerializationContext.context_for(self)
|
187
|
+
cmd_raw = lc.serialize(context)
|
188
|
+
|
189
|
+
if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
|
190
|
+
raise OffsetInsertionError, offset
|
191
|
+
end
|
192
|
+
|
193
|
+
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize
|
194
|
+
|
195
|
+
if header.class.bytesize + new_sizeofcmds > low_fileoff
|
196
|
+
raise HeaderPadError, @filename
|
197
|
+
end
|
198
|
+
|
199
|
+
# update Mach-O header fields to account for inserted load command
|
200
|
+
update_ncmds(ncmds + 1)
|
201
|
+
update_sizeofcmds(new_sizeofcmds)
|
202
|
+
|
203
|
+
@raw_data.insert(offset, cmd_raw)
|
204
|
+
@raw_data.slice!(header.class.bytesize + new_sizeofcmds, cmd_raw.bytesize)
|
205
|
+
|
206
|
+
populate_fields if options.fetch(:repopulate, true)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Replace a load command with another command in the Mach-O, preserving location.
|
210
|
+
# @param old_lc [MachO::LoadCommand] the load command being replaced
|
211
|
+
# @param new_lc [MachO::LoadCommand] the load command being added
|
212
|
+
# @return [void]
|
213
|
+
# @raise [MachO::HeaderPadError] if the new command exceeds the header pad buffer
|
214
|
+
# @see {#insert_command}
|
215
|
+
# @note This is public, but methods like {#dylib_id=} should be preferred.
|
216
|
+
def replace_command(old_lc, new_lc)
|
217
|
+
context = LoadCommand::SerializationContext.context_for(self)
|
218
|
+
cmd_raw = new_lc.serialize(context)
|
219
|
+
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
|
220
|
+
if header.class.bytesize + new_sizeofcmds > low_fileoff
|
221
|
+
raise HeaderPadError, @filename
|
222
|
+
end
|
223
|
+
|
224
|
+
delete_command(old_lc)
|
225
|
+
insert_command(old_lc.view.offset, new_lc)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Appends a new load command to the Mach-O.
|
229
|
+
# @param lc [MachO::LoadCommand] the load command being added
|
230
|
+
# @param options [Hash]
|
231
|
+
# @option options [Boolean] :repopulate (true) whether or not to repopulate
|
232
|
+
# the instance fields
|
233
|
+
# @return [void]
|
234
|
+
# @see {#insert_command}
|
235
|
+
# @note This is public, but methods like {#add_rpath} should be preferred.
|
236
|
+
# Setting `repopulate` to false **will leave the instance in an
|
237
|
+
# inconsistent state** unless {#populate_fields} is called **immediately**
|
238
|
+
# afterwards.
|
239
|
+
def add_command(lc, options = {})
|
240
|
+
insert_command(header.class.bytesize + sizeofcmds, lc, options)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Delete a load command from the Mach-O.
|
244
|
+
# @param lc [MachO::LoadCommand] the load command being deleted
|
245
|
+
# @param options [Hash]
|
246
|
+
# @option options [Boolean] :repopulate (true) whether or not to repopulate
|
247
|
+
# the instance fields
|
248
|
+
# @return [void]
|
249
|
+
# @note This is public, but methods like {#delete_rpath} should be preferred.
|
250
|
+
# Setting `repopulate` to false **will leave the instance in an
|
251
|
+
# inconsistent state** unless {#populate_fields} is called **immediately**
|
252
|
+
# afterwards.
|
253
|
+
def delete_command(lc, options = {})
|
254
|
+
@raw_data.slice!(lc.view.offset, lc.cmdsize)
|
255
|
+
|
256
|
+
# update Mach-O header fields to account for deleted load command
|
257
|
+
update_ncmds(ncmds - 1)
|
258
|
+
update_sizeofcmds(sizeofcmds - lc.cmdsize)
|
259
|
+
|
260
|
+
# pad the space after the load commands to preserve offsets
|
261
|
+
null_pad = "\x00" * lc.cmdsize
|
262
|
+
@raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad)
|
263
|
+
|
264
|
+
populate_fields if options.fetch(:repopulate, true)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Populate the instance's fields with the raw Mach-O data.
|
268
|
+
# @return [void]
|
269
|
+
# @note This method is public, but should (almost) never need to be called.
|
270
|
+
# The exception to this rule is when methods like {#add_command} and
|
271
|
+
# {#delete_command} have been called with `repopulate = false`.
|
272
|
+
def populate_fields
|
273
|
+
@header = populate_mach_header
|
274
|
+
@load_commands = populate_load_commands
|
275
|
+
end
|
168
276
|
|
169
277
|
# All load commands responsible for loading dylibs.
|
170
278
|
# @return [Array<MachO::DylibCommand>] an array of DylibCommands
|
@@ -188,9 +296,7 @@ module MachO
|
|
188
296
|
# file.dylib_id # => 'libBar.dylib'
|
189
297
|
# @return [String, nil] the Mach-O's dylib ID
|
190
298
|
def dylib_id
|
191
|
-
|
192
|
-
return nil
|
193
|
-
end
|
299
|
+
return unless dylib?
|
194
300
|
|
195
301
|
dylib_id_cmd = command(:LC_ID_DYLIB).first
|
196
302
|
|
@@ -199,25 +305,30 @@ module MachO
|
|
199
305
|
|
200
306
|
# Changes the Mach-O's dylib ID to `new_id`. Does nothing if not a dylib.
|
201
307
|
# @example
|
202
|
-
# file.
|
308
|
+
# file.change_dylib_id("libFoo.dylib")
|
203
309
|
# @param new_id [String] the dylib's new ID
|
310
|
+
# @param _options [Hash]
|
204
311
|
# @return [void]
|
205
312
|
# @raise [ArgumentError] if `new_id` is not a String
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
313
|
+
# @note `_options` is currently unused and is provided for signature
|
314
|
+
# compatibility with {MachO::FatFile#change_dylib_id}
|
315
|
+
def change_dylib_id(new_id, _options = {})
|
316
|
+
raise ArgumentError, "new ID must be a String" unless new_id.is_a?(String)
|
317
|
+
return unless dylib?
|
210
318
|
|
211
|
-
|
212
|
-
|
213
|
-
end
|
319
|
+
old_lc = command(:LC_ID_DYLIB).first
|
320
|
+
raise DylibIdMissingError unless old_lc
|
214
321
|
|
215
|
-
|
216
|
-
|
322
|
+
new_lc = LoadCommand.create(:LC_ID_DYLIB, new_id,
|
323
|
+
old_lc.timestamp,
|
324
|
+
old_lc.current_version,
|
325
|
+
old_lc.compatibility_version)
|
217
326
|
|
218
|
-
|
327
|
+
replace_command(old_lc, new_lc)
|
219
328
|
end
|
220
329
|
|
330
|
+
alias dylib_id= change_dylib_id
|
331
|
+
|
221
332
|
# All shared libraries linked to the Mach-O.
|
222
333
|
# @return [Array<String>] an array of all shared libraries
|
223
334
|
def linked_dylibs
|
@@ -233,16 +344,24 @@ module MachO
|
|
233
344
|
# file.change_install_name("/usr/lib/libWhatever.dylib", "/usr/local/lib/libWhatever2.dylib")
|
234
345
|
# @param old_name [String] the shared library's old name
|
235
346
|
# @param new_name [String] the shared library's new name
|
347
|
+
# @param _options [Hash]
|
236
348
|
# @return [void]
|
237
349
|
# @raise [MachO::DylibUnknownError] if no shared library has the old name
|
238
|
-
|
239
|
-
|
240
|
-
|
350
|
+
# @note `_options` is currently unused and is provided for signature
|
351
|
+
# compatibility with {MachO::FatFile#change_install_name}
|
352
|
+
def change_install_name(old_name, new_name, _options = {})
|
353
|
+
old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name }
|
354
|
+
raise DylibUnknownError, old_name if old_lc.nil?
|
241
355
|
|
242
|
-
|
356
|
+
new_lc = LoadCommand.create(old_lc.type, new_name,
|
357
|
+
old_lc.timestamp,
|
358
|
+
old_lc.current_version,
|
359
|
+
old_lc.compatibility_version)
|
360
|
+
|
361
|
+
replace_command(old_lc, new_lc)
|
243
362
|
end
|
244
363
|
|
245
|
-
alias
|
364
|
+
alias change_dylib change_install_name
|
246
365
|
|
247
366
|
# All runtime paths searched by the dynamic linker for the Mach-O.
|
248
367
|
# @return [Array<String>] an array of all runtime paths
|
@@ -255,44 +374,70 @@ module MachO
|
|
255
374
|
# file.change_rpath("/usr/lib", "/usr/local/lib")
|
256
375
|
# @param old_path [String] the old runtime path
|
257
376
|
# @param new_path [String] the new runtime path
|
377
|
+
# @param _options [Hash]
|
258
378
|
# @return [void]
|
259
379
|
# @raise [MachO::RpathUnknownError] if no such old runtime path exists
|
260
|
-
# @
|
261
|
-
|
262
|
-
|
263
|
-
|
380
|
+
# @raise [MachO::RpathExistsError] if the new runtime path already exists
|
381
|
+
# @note `_options` is currently unused and is provided for signature
|
382
|
+
# compatibility with {MachO::FatFile#change_rpath}
|
383
|
+
def change_rpath(old_path, new_path, _options = {})
|
384
|
+
old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
|
385
|
+
raise RpathUnknownError, old_path if old_lc.nil?
|
386
|
+
raise RpathExistsError, new_path if rpaths.include?(new_path)
|
387
|
+
|
388
|
+
new_lc = LoadCommand.create(:LC_RPATH, new_path)
|
389
|
+
|
390
|
+
delete_rpath(old_path)
|
391
|
+
insert_command(old_lc.view.offset, new_lc)
|
392
|
+
end
|
264
393
|
|
265
|
-
|
394
|
+
# Add the given runtime path to the Mach-O.
|
395
|
+
# @example
|
396
|
+
# file.rpaths # => ["/lib"]
|
397
|
+
# file.add_rpath("/usr/lib")
|
398
|
+
# file.rpaths # => ["/lib", "/usr/lib"]
|
399
|
+
# @param path [String] the new runtime path
|
400
|
+
# @param _options [Hash]
|
401
|
+
# @return [void]
|
402
|
+
# @raise [MachO::RpathExistsError] if the runtime path already exists
|
403
|
+
# @note `_options` is currently unused and is provided for signature
|
404
|
+
# compatibility with {MachO::FatFile#add_rpath}
|
405
|
+
def add_rpath(path, _options = {})
|
406
|
+
raise RpathExistsError, path if rpaths.include?(path)
|
407
|
+
|
408
|
+
rpath_cmd = LoadCommand.create(:LC_RPATH, path)
|
409
|
+
add_command(rpath_cmd)
|
410
|
+
end
|
411
|
+
|
412
|
+
# Delete the given runtime path from the Mach-O.
|
413
|
+
# @example
|
414
|
+
# file.rpaths # => ["/lib"]
|
415
|
+
# file.delete_rpath("/lib")
|
416
|
+
# file.rpaths # => []
|
417
|
+
# @param path [String] the runtime path to delete
|
418
|
+
# @param _options [Hash]
|
419
|
+
# @return void
|
420
|
+
# @raise [MachO::RpathUnknownError] if no such runtime path exists
|
421
|
+
# @note `_options` is currently unused and is provided for signature
|
422
|
+
# compatibility with {MachO::FatFile#delete_rpath}
|
423
|
+
def delete_rpath(path, _options = {})
|
424
|
+
rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
|
425
|
+
raise RpathUnknownError, path if rpath_cmds.empty?
|
426
|
+
|
427
|
+
# delete the commands in reverse order, offset descending. this
|
428
|
+
# allows us to defer (expensive) field population until the very end
|
429
|
+
rpath_cmds.reverse_each { |cmd| delete_command(cmd, :repopulate => false) }
|
430
|
+
|
431
|
+
populate_fields
|
266
432
|
end
|
267
433
|
|
268
434
|
# All sections of the segment `segment`.
|
269
435
|
# @param segment [MachO::SegmentCommand, MachO::SegmentCommand64] the segment being inspected
|
270
436
|
# @return [Array<MachO::Section>] if the Mach-O is 32-bit
|
271
437
|
# @return [Array<MachO::Section64>] if the Mach-O is 64-bit
|
438
|
+
# @deprecated use {MachO::SegmentCommand#sections} instead
|
272
439
|
def sections(segment)
|
273
|
-
sections
|
274
|
-
|
275
|
-
if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
|
276
|
-
raise ArgumentError.new("not a valid segment")
|
277
|
-
end
|
278
|
-
|
279
|
-
if segment.nsects.zero?
|
280
|
-
return sections
|
281
|
-
end
|
282
|
-
|
283
|
-
offset = segment.offset + segment.class.bytesize
|
284
|
-
|
285
|
-
segment.nsects.times do
|
286
|
-
if segment.is_a? SegmentCommand
|
287
|
-
sections << Section.new_from_bin(endianness, @raw_data.slice(offset, Section.bytesize))
|
288
|
-
offset += Section.bytesize
|
289
|
-
else
|
290
|
-
sections << Section64.new_from_bin(endianness, @raw_data.slice(offset, Section64.bytesize))
|
291
|
-
offset += Section64.bytesize
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
sections
|
440
|
+
segment.sections
|
296
441
|
end
|
297
442
|
|
298
443
|
# Write all Mach-O data to the given filename.
|
@@ -308,7 +453,7 @@ module MachO
|
|
308
453
|
# @note Overwrites all data in the file!
|
309
454
|
def write!
|
310
455
|
if @filename.nil?
|
311
|
-
raise MachOError
|
456
|
+
raise MachOError, "cannot write to a default file when initialized from a binary string"
|
312
457
|
else
|
313
458
|
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
314
459
|
end
|
@@ -320,13 +465,13 @@ module MachO
|
|
320
465
|
# @return [MachO::MachHeader] if the Mach-O is 32-bit
|
321
466
|
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
|
322
467
|
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
|
323
|
-
# @private
|
324
|
-
def
|
468
|
+
# @api private
|
469
|
+
def populate_mach_header
|
325
470
|
# the smallest Mach-O header is 28 bytes
|
326
|
-
raise TruncatedFileError
|
471
|
+
raise TruncatedFileError if @raw_data.size < 28
|
327
472
|
|
328
|
-
magic =
|
329
|
-
mh_klass =
|
473
|
+
magic = populate_and_check_magic
|
474
|
+
mh_klass = Utils.magic32?(magic) ? MachHeader : MachHeader64
|
330
475
|
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
|
331
476
|
|
332
477
|
check_cputype(mh.cputype)
|
@@ -340,14 +485,14 @@ module MachO
|
|
340
485
|
# @return [Fixnum] the magic
|
341
486
|
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic
|
342
487
|
# @raise [MachO::FatBinaryError] if the magic is for a Fat file
|
343
|
-
# @private
|
344
|
-
def
|
488
|
+
# @api private
|
489
|
+
def populate_and_check_magic
|
345
490
|
magic = @raw_data[0..3].unpack("N").first
|
346
491
|
|
347
|
-
raise MagicError
|
348
|
-
raise FatBinaryError
|
492
|
+
raise MagicError, magic unless Utils.magic?(magic)
|
493
|
+
raise FatBinaryError if Utils.fat_magic?(magic)
|
349
494
|
|
350
|
-
@endianness =
|
495
|
+
@endianness = Utils.little_magic?(magic) ? :little : :big
|
351
496
|
|
352
497
|
magic
|
353
498
|
end
|
@@ -355,47 +500,48 @@ module MachO
|
|
355
500
|
# Check the file's CPU type.
|
356
501
|
# @param cputype [Fixnum] the CPU type
|
357
502
|
# @raise [MachO::CPUTypeError] if the CPU type is unknown
|
358
|
-
# @private
|
503
|
+
# @api private
|
359
504
|
def check_cputype(cputype)
|
360
|
-
raise CPUTypeError
|
505
|
+
raise CPUTypeError, cputype unless CPU_TYPES.key?(cputype)
|
361
506
|
end
|
362
507
|
|
363
508
|
# Check the file's CPU type/subtype pair.
|
364
509
|
# @param cpusubtype [Fixnum] the CPU subtype
|
365
510
|
# @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown
|
366
|
-
# @private
|
511
|
+
# @api private
|
367
512
|
def check_cpusubtype(cputype, cpusubtype)
|
368
|
-
# Only check sub-type w/o capability bits (see `
|
513
|
+
# Only check sub-type w/o capability bits (see `populate_mach_header`).
|
369
514
|
raise CPUSubtypeError.new(cputype, cpusubtype) unless CPU_SUBTYPES[cputype].key?(cpusubtype)
|
370
515
|
end
|
371
516
|
|
372
517
|
# Check the file's type.
|
373
518
|
# @param filetype [Fixnum] the file type
|
374
519
|
# @raise [MachO::FiletypeError] if the file type is unknown
|
375
|
-
# @private
|
520
|
+
# @api private
|
376
521
|
def check_filetype(filetype)
|
377
|
-
raise FiletypeError
|
522
|
+
raise FiletypeError, filetype unless MH_FILETYPES.key?(filetype)
|
378
523
|
end
|
379
524
|
|
380
525
|
# All load commands in the file.
|
381
526
|
# @return [Array<MachO::LoadCommand>] an array of load commands
|
382
527
|
# @raise [MachO::LoadCommandError] if an unknown load command is encountered
|
383
|
-
# @private
|
384
|
-
def
|
528
|
+
# @api private
|
529
|
+
def populate_load_commands
|
385
530
|
offset = header.class.bytesize
|
386
531
|
load_commands = []
|
387
532
|
|
388
533
|
header.ncmds.times do
|
389
|
-
fmt = (
|
534
|
+
fmt = Utils.specialize_format("L=", endianness)
|
390
535
|
cmd = @raw_data.slice(offset, 4).unpack(fmt).first
|
391
536
|
cmd_sym = LOAD_COMMANDS[cmd]
|
392
537
|
|
393
|
-
raise LoadCommandError
|
538
|
+
raise LoadCommandError, cmd if cmd_sym.nil?
|
394
539
|
|
395
540
|
# why do I do this? i don't like declaring constants below
|
396
541
|
# classes, and i need them to resolve...
|
397
|
-
klass = MachO.const_get
|
398
|
-
|
542
|
+
klass = MachO.const_get LC_STRUCTURES[cmd_sym]
|
543
|
+
view = MachOView.new(@raw_data, endianness, offset)
|
544
|
+
command = klass.new_from_bin(view)
|
399
545
|
|
400
546
|
load_commands << command
|
401
547
|
offset += command.cmdsize
|
@@ -404,108 +550,44 @@ module MachO
|
|
404
550
|
load_commands
|
405
551
|
end
|
406
552
|
|
407
|
-
#
|
408
|
-
# @
|
409
|
-
# @
|
410
|
-
|
411
|
-
|
412
|
-
fmt = (endianness == :little) ? "L<" : "L>"
|
413
|
-
new_size = [size].pack(fmt)
|
414
|
-
@raw_data[20..23] = new_size
|
415
|
-
end
|
416
|
-
|
417
|
-
# Updates the `name` field in a DylibCommand.
|
418
|
-
# @param dylib_cmd [MachO::DylibCommand] the dylib command
|
419
|
-
# @param old_name [String] the old dylib name
|
420
|
-
# @param new_name [String] the new dylib name
|
421
|
-
# @return [void]
|
422
|
-
# @private
|
423
|
-
def set_name_in_dylib(dylib_cmd, old_name, new_name)
|
424
|
-
set_lc_str_in_cmd(dylib_cmd, dylib_cmd.name, old_name, new_name)
|
425
|
-
end
|
426
|
-
|
427
|
-
# Updates the `path` field in an RpathCommand.
|
428
|
-
# @param rpath_cmd [MachO::RpathCommand] the rpath command
|
429
|
-
# @param old_path [String] the old runtime name
|
430
|
-
# @param new_path [String] the new runtime name
|
431
|
-
# @return [void]
|
432
|
-
# @private
|
433
|
-
def set_path_in_rpath(rpath_cmd, old_path, new_path)
|
434
|
-
set_lc_str_in_cmd(rpath_cmd, rpath_cmd.path, old_path, new_path)
|
435
|
-
end
|
436
|
-
|
437
|
-
# Updates a generic LCStr field in any LoadCommand.
|
438
|
-
# @param cmd [MachO::LoadCommand] the load command
|
439
|
-
# @param lc_str [MachO::LoadCommand::LCStr] the load command string
|
440
|
-
# @param old_str [String] the old string
|
441
|
-
# @param new_str [String] the new string
|
442
|
-
# @raise [MachO::HeaderPadError] if the new name exceeds the header pad buffer
|
443
|
-
# @private
|
444
|
-
def set_lc_str_in_cmd(cmd, lc_str, old_str, new_str)
|
445
|
-
if magic32?
|
446
|
-
cmd_round = 4
|
447
|
-
else
|
448
|
-
cmd_round = 8
|
449
|
-
end
|
450
|
-
|
451
|
-
new_sizeofcmds = header.sizeofcmds
|
452
|
-
old_str = old_str.dup
|
453
|
-
new_str = new_str.dup
|
454
|
-
|
455
|
-
old_pad = MachO.round(old_str.size + 1, cmd_round) - old_str.size
|
456
|
-
new_pad = MachO.round(new_str.size + 1, cmd_round) - new_str.size
|
457
|
-
|
458
|
-
# pad the old and new IDs with null bytes to meet command bounds
|
459
|
-
old_str << "\x00" * old_pad
|
460
|
-
new_str << "\x00" * new_pad
|
461
|
-
|
462
|
-
# calculate the new size of the cmd and sizeofcmds in MH
|
463
|
-
new_size = cmd.class.bytesize + new_str.size
|
464
|
-
new_sizeofcmds += new_size - cmd.cmdsize
|
465
|
-
|
466
|
-
low_fileoff = @raw_data.size
|
553
|
+
# The low file offset (offset to first section data).
|
554
|
+
# @return [Fixnum] the offset
|
555
|
+
# @api private
|
556
|
+
def low_fileoff
|
557
|
+
offset = @raw_data.size
|
467
558
|
|
468
|
-
# calculate the low file offset (offset to first section data)
|
469
559
|
segments.each do |seg|
|
470
|
-
|
471
|
-
next if sect.
|
560
|
+
seg.sections.each do |sect|
|
561
|
+
next if sect.empty?
|
472
562
|
next if sect.flag?(:S_ZEROFILL)
|
473
563
|
next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
|
474
|
-
next unless sect.offset <
|
564
|
+
next unless sect.offset < offset
|
475
565
|
|
476
|
-
|
566
|
+
offset = sect.offset
|
477
567
|
end
|
478
568
|
end
|
479
569
|
|
480
|
-
|
481
|
-
|
482
|
-
end
|
483
|
-
|
484
|
-
# update sizeofcmds in mach_header
|
485
|
-
set_sizeofcmds(new_sizeofcmds)
|
486
|
-
|
487
|
-
# update cmdsize in the cmd
|
488
|
-
fmt = (endianness == :little) ? "L<" : "L>"
|
489
|
-
@raw_data[cmd.offset + 4, 4] = [new_size].pack(fmt)
|
490
|
-
|
491
|
-
# delete the old str
|
492
|
-
@raw_data.slice!(cmd.offset + lc_str.to_i...cmd.offset + cmd.class.bytesize + old_str.size)
|
493
|
-
|
494
|
-
# insert the new str
|
495
|
-
@raw_data.insert(cmd.offset + lc_str.to_i, new_str)
|
496
|
-
|
497
|
-
# pad/unpad after new_sizeofcmds until offsets are corrected
|
498
|
-
null_pad = old_str.size - new_str.size
|
570
|
+
offset
|
571
|
+
end
|
499
572
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
573
|
+
# Updates the number of load commands in the raw data.
|
574
|
+
# @param ncmds [Fixnum] the new number of commands
|
575
|
+
# @return [void]
|
576
|
+
# @api private
|
577
|
+
def update_ncmds(ncmds)
|
578
|
+
fmt = Utils.specialize_format("L=", endianness)
|
579
|
+
ncmds_raw = [ncmds].pack(fmt)
|
580
|
+
@raw_data[16..19] = ncmds_raw
|
581
|
+
end
|
505
582
|
|
506
|
-
|
507
|
-
|
508
|
-
|
583
|
+
# Updates the size of all load commands in the raw data.
|
584
|
+
# @param size [Fixnum] the new size, in bytes
|
585
|
+
# @return [void]
|
586
|
+
# @api private
|
587
|
+
def update_sizeofcmds(size)
|
588
|
+
fmt = Utils.specialize_format("L=", endianness)
|
589
|
+
size_raw = [size].pack(fmt)
|
590
|
+
@raw_data[20..23] = size_raw
|
509
591
|
end
|
510
592
|
end
|
511
593
|
end
|