ruby-macho 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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