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.
@@ -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.new("#{filename}: no such file") unless File.file?(filename)
36
+ raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
36
37
 
37
38
  @filename = filename
38
- @raw_data = File.open(@filename, "rb") { |f| f.read }
39
- @header = get_mach_header
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
- @header = get_mach_header
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
- MachO.magic32?(header.magic)
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
- MachO.magic64?(header.magic)
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 [String] a string representation of the Mach-O's filetype
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 :[] :command
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
- if !dylib?
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.dylib_id = "libFoo.dylib"
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
- def dylib_id=(new_id)
207
- if !new_id.is_a?(String)
208
- raise ArgumentError.new("argument must be a String")
209
- end
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
- if !dylib?
212
- return nil
213
- end
319
+ old_lc = command(:LC_ID_DYLIB).first
320
+ raise DylibIdMissingError unless old_lc
214
321
 
215
- dylib_cmd = command(:LC_ID_DYLIB).first
216
- old_id = dylib_id
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
- set_name_in_dylib(dylib_cmd, old_id, new_id)
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
- def change_install_name(old_name, new_name)
239
- dylib_cmd = dylib_load_commands.find { |d| d.name.to_s == old_name }
240
- raise DylibUnknownError.new(old_name) if dylib_cmd.nil?
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
- set_name_in_dylib(dylib_cmd, old_name, new_name)
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 :change_dylib :change_install_name
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
- # @api private
261
- def change_rpath(old_path, new_path)
262
- rpath_cmd = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
263
- raise RpathUnknownError.new(old_path) if rpath_cmd.nil?
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
- set_path_in_rpath(rpath_cmd, old_path, new_path)
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.new("cannot write to a default file when initialized from a binary string")
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 get_mach_header
468
+ # @api private
469
+ def populate_mach_header
325
470
  # the smallest Mach-O header is 28 bytes
326
- raise TruncatedFileError.new if @raw_data.size < 28
471
+ raise TruncatedFileError if @raw_data.size < 28
327
472
 
328
- magic = get_and_check_magic
329
- mh_klass = MachO.magic32?(magic) ? MachHeader : MachHeader64
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 get_and_check_magic
488
+ # @api private
489
+ def populate_and_check_magic
345
490
  magic = @raw_data[0..3].unpack("N").first
346
491
 
347
- raise MagicError.new(magic) unless MachO.magic?(magic)
348
- raise FatBinaryError.new if MachO.fat_magic?(magic)
492
+ raise MagicError, magic unless Utils.magic?(magic)
493
+ raise FatBinaryError if Utils.fat_magic?(magic)
349
494
 
350
- @endianness = MachO.little_magic?(magic) ? :little : :big
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.new(cputype) unless CPU_TYPES.key?(cputype)
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 `get_mach_header`).
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.new(filetype) unless MH_FILETYPES.key?(filetype)
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 get_load_commands
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 = (endianness == :little) ? "L<" : "L>"
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.new(cmd) if cmd_sym.nil?
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 "#{LC_STRUCTURES[cmd_sym]}"
398
- command = klass.new_from_bin(@raw_data, endianness, offset, @raw_data.slice(offset, klass.bytesize))
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
- # Updates the size of all load commands in the raw data.
408
- # @param size [Fixnum] the new size, in bytes
409
- # @return [void]
410
- # @private
411
- def set_sizeofcmds(size)
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
- sections(seg).each do |sect|
471
- next if sect.size == 0
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 < low_fileoff
564
+ next unless sect.offset < offset
475
565
 
476
- low_fileoff = sect.offset
566
+ offset = sect.offset
477
567
  end
478
568
  end
479
569
 
480
- if new_sizeofcmds + header.class.bytesize > low_fileoff
481
- raise HeaderPadError.new(@filename)
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
- if null_pad < 0
501
- @raw_data.slice!(new_sizeofcmds + header.class.bytesize, null_pad.abs)
502
- else
503
- @raw_data.insert(new_sizeofcmds + header.class.bytesize, "\x00" * null_pad)
504
- end
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
- # synchronize fields with the raw data
507
- @header = get_mach_header
508
- @load_commands = get_load_commands
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