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.
@@ -1,6 +1,7 @@
1
1
  module MachO
2
2
  # load commands added after OS X 10.1 need to be bitwise ORed with
3
3
  # LC_REQ_DYLD to be recognized by the dynamic linder (dyld)
4
+ # @api private
4
5
  LC_REQ_DYLD = 0x80000000
5
6
 
6
7
  # association of load commands to symbol representations
@@ -57,6 +58,10 @@ module MachO
57
58
  0x30 => :LC_VERSION_MIN_WATCHOS,
58
59
  }.freeze
59
60
 
61
+ # association of symbol representations to load command constants
62
+ # @api private
63
+ LOAD_COMMAND_CONSTANTS = LOAD_COMMANDS.invert.freeze
64
+
60
65
  # load commands responsible for loading dylibs
61
66
  # @api private
62
67
  DYLIB_LOAD_COMMANDS = [
@@ -67,19 +72,27 @@ module MachO
67
72
  :LC_LOAD_UPWARD_DYLIB,
68
73
  ].freeze
69
74
 
75
+ # load commands that can be created manually via {LoadCommand.create}
76
+ # @api private
77
+ CREATABLE_LOAD_COMMANDS = DYLIB_LOAD_COMMANDS + [
78
+ :LC_ID_DYLIB,
79
+ :LC_RPATH,
80
+ :LC_LOAD_DYLINKER,
81
+ ].freeze
82
+
70
83
  # association of load command symbols to string representations of classes
71
84
  # @api private
72
85
  LC_STRUCTURES = {
73
86
  :LC_SEGMENT => "SegmentCommand",
74
87
  :LC_SYMTAB => "SymtabCommand",
75
- :LC_SYMSEG => "LoadCommand", # obsolete
88
+ :LC_SYMSEG => "SymsegCommand", # obsolete
76
89
  :LC_THREAD => "ThreadCommand", # seems obsolete, but not documented as such
77
90
  :LC_UNIXTHREAD => "ThreadCommand",
78
- :LC_LOADFVMLIB => "LoadCommand", # obsolete
79
- :LC_IDFVMLIB => "LoadCommand", # obsolete
80
- :LC_IDENT => "LoadCommand", # obsolete
81
- :LC_FVMFILE => "LoadCommand", # reserved for internal use only
82
- :LC_PREPAGE => "LoadCommand", # reserved for internal use only
91
+ :LC_LOADFVMLIB => "FvmlibCommand", # obsolete
92
+ :LC_IDFVMLIB => "FvmlibCommand", # obsolete
93
+ :LC_IDENT => "IdentCommand", # obsolete
94
+ :LC_FVMFILE => "FvmfileCommand", # reserved for internal use only
95
+ :LC_PREPAGE => "LoadCommand", # reserved for internal use only, no public struct
83
96
  :LC_DYSYMTAB => "DysymtabCommand",
84
97
  :LC_LOAD_DYLIB => "DylibCommand",
85
98
  :LC_ID_DYLIB => "DylibCommand",
@@ -148,8 +161,8 @@ module MachO
148
161
  # represented, and no actual data. Used when a more specific class
149
162
  # isn't available/implemented.
150
163
  class LoadCommand < MachOStructure
151
- # @return [Fixnum] the offset in the file the command was created from
152
- attr_reader :offset
164
+ # @return [MachO::MachOView] the raw view associated with the load command
165
+ attr_reader :view
153
166
 
154
167
  # @return [Fixnum] the load command's identifying number
155
168
  attr_reader :cmd
@@ -157,40 +170,80 @@ module MachO
157
170
  # @return [Fixnum] the size of the load command, in bytes
158
171
  attr_reader :cmdsize
159
172
 
160
- FORMAT = "L=2"
173
+ # @see MachOStructure::FORMAT
174
+ # @api private
175
+ FORMAT = "L=2".freeze
176
+
177
+ # @see MachOStructure::SIZEOF
178
+ # @api private
161
179
  SIZEOF = 8
162
180
 
163
- # Creates a new LoadCommand given an offset and binary string
164
- # @param raw_data [String] the raw Mach-O data
165
- # @param endianness [Symbol] the endianness of the command (:big or :little)
166
- # @param offset [Fixnum] the offset to initialize with
167
- # @param bin [String] the binary string to initialize with
181
+ # Instantiates a new LoadCommand given a view into its origin Mach-O
182
+ # @param view [MachO::MachOView] the load command's raw view
168
183
  # @return [MachO::LoadCommand] the new load command
169
184
  # @api private
170
- def self.new_from_bin(raw_data, endianness, offset, bin)
171
- format = specialize_format(self::FORMAT, endianness)
185
+ def self.new_from_bin(view)
186
+ bin = view.raw_data.slice(view.offset, bytesize)
187
+ format = Utils.specialize_format(self::FORMAT, view.endianness)
188
+
189
+ new(view, *bin.unpack(format))
190
+ end
191
+
192
+ # Creates a new (viewless) command corresponding to the symbol provided
193
+ # @param cmd_sym [Symbol] the symbol of the load command being created
194
+ # @param args [Array] the arguments for the load command being created
195
+ def self.create(cmd_sym, *args)
196
+ raise LoadCommandNotCreatableError, cmd_sym unless CREATABLE_LOAD_COMMANDS.include?(cmd_sym)
197
+
198
+ klass = MachO.const_get LC_STRUCTURES[cmd_sym]
199
+ cmd = LOAD_COMMAND_CONSTANTS[cmd_sym]
172
200
 
173
- self.new(raw_data, offset, *bin.unpack(format))
201
+ # cmd will be filled in, view and cmdsize will be left unpopulated
202
+ klass_arity = klass.instance_method(:initialize).arity - 3
203
+
204
+ raise LoadCommandCreationArityError.new(cmd_sym, klass_arity, args.size) if klass_arity != args.size
205
+
206
+ klass.new(nil, cmd, nil, *args)
174
207
  end
175
208
 
176
- # @param raw_data [String] the raw Mach-O data
177
- # @param offset [Fixnum] the offset to initialize with
209
+ # @param view [MachO::MachOView] the load command's raw view
178
210
  # @param cmd [Fixnum] the load command's identifying number
179
211
  # @param cmdsize [Fixnum] the size of the load command in bytes
180
212
  # @api private
181
- def initialize(raw_data, offset, cmd, cmdsize)
182
- @raw_data = raw_data
183
- @offset = offset
213
+ def initialize(view, cmd, cmdsize)
214
+ @view = view
184
215
  @cmd = cmd
185
216
  @cmdsize = cmdsize
186
217
  end
187
218
 
219
+ # @return [Boolean] true if the load command can be serialized, false otherwise
220
+ def serializable?
221
+ CREATABLE_LOAD_COMMANDS.include?(LOAD_COMMANDS[cmd])
222
+ end
223
+
224
+ # @param context [MachO::LoadCommand::SerializationContext] the context
225
+ # to serialize into
226
+ # @return [String, nil] the serialized fields of the load command, or nil
227
+ # if the load command can't be serialized
228
+ # @api private
229
+ def serialize(context)
230
+ raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable?
231
+ format = Utils.specialize_format(FORMAT, context.endianness)
232
+ [cmd, SIZEOF].pack(format)
233
+ end
234
+
235
+ # @return [Fixnum] the load command's offset in the source file
236
+ # @deprecated use {#view} instead
237
+ def offset
238
+ view.offset
239
+ end
240
+
188
241
  # @return [Symbol] a symbol representation of the load command's identifying number
189
242
  def type
190
243
  LOAD_COMMANDS[cmd]
191
244
  end
192
245
 
193
- alias :to_sym :type
246
+ alias to_sym type
194
247
 
195
248
  # @return [String] a string representation of the load command's identifying number
196
249
  def to_s
@@ -202,25 +255,62 @@ module MachO
202
255
  # pretend that strings stored in LCs are immediately available without
203
256
  # explicit operations on the raw Mach-O data.
204
257
  class LCStr
205
- # @param raw_data [String] the raw Mach-O data.
206
258
  # @param lc [MachO::LoadCommand] the load command
207
- # @param lc_str [Fixnum] the offset to the beginning of the string
259
+ # @param lc_str [Fixnum, String] the offset to the beginning of the string,
260
+ # or the string itself if not being initialized with a view.
261
+ # @raise [MachO::LCStrMalformedError] if the string is malformed
262
+ # @todo devise a solution such that the `lc_str` parameter is not
263
+ # interpreted differently depending on `lc.view`. The current behavior
264
+ # is a hack to allow viewless load command creation.
208
265
  # @api private
209
- def initialize(raw_data, lc, lc_str)
210
- @raw_data = raw_data
211
- @lc = lc
212
- @lc_str = lc_str
213
- @str = @raw_data.slice(@lc.offset + @lc_str...@lc.offset + @lc.cmdsize).delete("\x00")
266
+ def initialize(lc, lc_str)
267
+ view = lc.view
268
+
269
+ if view
270
+ lc_str_abs = view.offset + lc_str
271
+ lc_end = view.offset + lc.cmdsize - 1
272
+ raw_string = view.raw_data.slice(lc_str_abs..lc_end)
273
+ @string, null_byte, _padding = raw_string.partition("\x00")
274
+ raise LCStrMalformedError, lc if null_byte.empty?
275
+ @string_offset = lc_str
276
+ else
277
+ @string = lc_str
278
+ @string_offset = 0
279
+ end
214
280
  end
215
281
 
216
282
  # @return [String] a string representation of the LCStr
217
283
  def to_s
218
- @str
284
+ @string
219
285
  end
220
286
 
221
287
  # @return [Fixnum] the offset to the beginning of the string in the load command
222
288
  def to_i
223
- @lc_str
289
+ @string_offset
290
+ end
291
+ end
292
+
293
+ # Represents the contextual information needed by a load command to
294
+ # serialize itself correctly into a binary string.
295
+ class SerializationContext
296
+ # @return [Symbol] the endianness of the serialized load command
297
+ attr_reader :endianness
298
+
299
+ # @return [Fixnum] the constant alignment value used to pad the serialized load command
300
+ attr_reader :alignment
301
+
302
+ # @param macho [MachO::MachOFile] the file to contextualize
303
+ # @return [MachO::LoadCommand::SerializationContext] the resulting context
304
+ def self.context_for(macho)
305
+ new(macho.endianness, macho.alignment)
306
+ end
307
+
308
+ # @param endianness [Symbol] the endianness of the context
309
+ # @param alignment [Fixnum] the alignment of the context
310
+ # @api private
311
+ def initialize(endianness, alignment)
312
+ @endianness = endianness
313
+ @alignment = alignment
224
314
  end
225
315
  end
226
316
  end
@@ -231,12 +321,17 @@ module MachO
231
321
  # @return [Array<Fixnum>] the UUID
232
322
  attr_reader :uuid
233
323
 
234
- FORMAT = "L=2a16"
324
+ # @see MachOStructure::FORMAT
325
+ # @api private
326
+ FORMAT = "L=2a16".freeze
327
+
328
+ # @see MachOStructure::SIZEOF
329
+ # @api private
235
330
  SIZEOF = 24
236
331
 
237
332
  # @api private
238
- def initialize(raw_data, offset, cmd, cmdsize, uuid)
239
- super(raw_data, offset, cmd, cmdsize)
333
+ def initialize(view, cmd, cmdsize, uuid)
334
+ super(view, cmd, cmdsize)
240
335
  @uuid = uuid.unpack("C16") # re-unpack for the actual UUID array
241
336
  end
242
337
 
@@ -282,13 +377,18 @@ module MachO
282
377
  # @return [Fixnum] any flags associated with the segment
283
378
  attr_reader :flags
284
379
 
285
- FORMAT = "L=2a16L=4l=2L=2"
380
+ # @see MachOStructure::FORMAT
381
+ # @api private
382
+ FORMAT = "L=2a16L=4l=2L=2".freeze
383
+
384
+ # @see MachOStructure::SIZEOF
385
+ # @api private
286
386
  SIZEOF = 56
287
387
 
288
388
  # @api private
289
- def initialize(raw_data, offset, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
290
- filesize, maxprot, initprot, nsects, flags)
291
- super(raw_data, offset, cmd, cmdsize)
389
+ def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
390
+ filesize, maxprot, initprot, nsects, flags)
391
+ super(view, cmd, cmdsize)
292
392
  @segname = segname.delete("\x00")
293
393
  @vmaddr = vmaddr
294
394
  @vmsize = vmsize
@@ -300,6 +400,23 @@ module MachO
300
400
  @flags = flags
301
401
  end
302
402
 
403
+ # All sections referenced within this segment.
404
+ # @return [Array<MachO::Section>] if the Mach-O is 32-bit
405
+ # @return [Array<MachO::Section64>] if the Mach-O is 64-bit
406
+ def sections
407
+ klass = case self
408
+ when MachO::SegmentCommand64
409
+ MachO::Section64
410
+ when MachO::SegmentCommand
411
+ MachO::Section
412
+ end
413
+
414
+ bins = view.raw_data[view.offset + self.class.bytesize, nsects * klass.bytesize]
415
+ bins.unpack("a#{klass.bytesize}" * nsects).map do |bin|
416
+ klass.new_from_bin(view.endianness, bin)
417
+ end
418
+ end
419
+
303
420
  # @example
304
421
  # puts "this segment relocated in/to it" if sect.flag?(:SG_NORELOC)
305
422
  # @param flag [Symbol] a segment flag symbol
@@ -313,61 +430,14 @@ module MachO
313
430
 
314
431
  # A load command indicating that part of this file is to be mapped into
315
432
  # the task's address space. Corresponds to LC_SEGMENT_64.
316
- class SegmentCommand64 < LoadCommand
317
- # @return [String] the name of the segment
318
- attr_reader :segname
319
-
320
- # @return [Fixnum] the memory address of the segment
321
- attr_reader :vmaddr
322
-
323
- # @return [Fixnum] the memory size of the segment
324
- attr_reader :vmsize
325
-
326
- # @return [Fixnum] the file offset of the segment
327
- attr_reader :fileoff
328
-
329
- # @return [Fixnum] the amount to map from the file
330
- attr_reader :filesize
331
-
332
- # @return [Fixnum] the maximum VM protection
333
- attr_reader :maxprot
334
-
335
- # @return [Fixnum] the initial VM protection
336
- attr_reader :initprot
337
-
338
- # @return [Fixnum] the number of sections in the segment
339
- attr_reader :nsects
340
-
341
- # @return [Fixnum] any flags associated with the segment
342
- attr_reader :flags
343
-
344
- FORMAT = "L=2a16Q=4l=2L=2"
345
- SIZEOF = 72
346
-
433
+ class SegmentCommand64 < SegmentCommand
434
+ # @see MachOStructure::FORMAT
347
435
  # @api private
348
- def initialize(raw_data, offset, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
349
- filesize, maxprot, initprot, nsects, flags)
350
- super(raw_data, offset, cmd, cmdsize)
351
- @segname = segname.delete("\x00")
352
- @vmaddr = vmaddr
353
- @vmsize = vmsize
354
- @fileoff = fileoff
355
- @filesize = filesize
356
- @maxprot = maxprot
357
- @initprot = initprot
358
- @nsects = nsects
359
- @flags = flags
360
- end
436
+ FORMAT = "L=2a16Q=4l=2L=2".freeze
361
437
 
362
- # @example
363
- # puts "this segment relocated in/to it" if sect.flag?(:SG_NORELOC)
364
- # @param flag [Symbol] a segment flag symbol
365
- # @return [Boolean] true if `flag` is present in the segment's flag field
366
- def flag?(flag)
367
- flag = SEGMENT_FLAGS[flag]
368
- return false if flag.nil?
369
- flags & flag == flag
370
- end
438
+ # @see MachOStructure::SIZEOF
439
+ # @api private
440
+ SIZEOF = 72
371
441
  end
372
442
 
373
443
  # A load command representing some aspect of shared libraries, depending
@@ -386,18 +456,33 @@ module MachO
386
456
  # @return [Fixnum] the library's compatibility version number
387
457
  attr_reader :compatibility_version
388
458
 
389
- FORMAT = "L=6"
459
+ # @see MachOStructure::FORMAT
460
+ # @api private
461
+ FORMAT = "L=6".freeze
462
+
463
+ # @see MachOStructure::SIZEOF
464
+ # @api private
390
465
  SIZEOF = 24
391
466
 
392
467
  # @api private
393
- def initialize(raw_data, offset, cmd, cmdsize, name, timestamp, current_version,
394
- compatibility_version)
395
- super(raw_data, offset, cmd, cmdsize)
396
- @name = LCStr.new(raw_data, self, name)
468
+ def initialize(view, cmd, cmdsize, name, timestamp, current_version, compatibility_version)
469
+ super(view, cmd, cmdsize)
470
+ @name = LCStr.new(self, name)
397
471
  @timestamp = timestamp
398
472
  @current_version = current_version
399
473
  @compatibility_version = compatibility_version
400
474
  end
475
+
476
+ # @param context [MachO::LoadCcommand::SerializationContext] the context
477
+ # @return [String] the serialized fields of the load command
478
+ # @api private
479
+ def serialize(context)
480
+ format = Utils.specialize_format(FORMAT, context.endianness)
481
+ string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s)
482
+ cmdsize = SIZEOF + string_payload.bytesize
483
+ [cmd, cmdsize, string_offsets[:name], timestamp, current_version,
484
+ compatibility_version].pack(format) + string_payload
485
+ end
401
486
  end
402
487
 
403
488
  # A load command representing some aspect of the dynamic linker, depending
@@ -407,13 +492,28 @@ module MachO
407
492
  # @return [MachO::LoadCommand::LCStr] the dynamic linker's path name as an LCStr
408
493
  attr_reader :name
409
494
 
410
- FORMAT = "L=3"
495
+ # @see MachOStructure::FORMAT
496
+ # @api private
497
+ FORMAT = "L=3".freeze
498
+
499
+ # @see MachOStructure::SIZEOF
500
+ # @api private
411
501
  SIZEOF = 12
412
502
 
413
503
  # @api private
414
- def initialize(raw_data, offset, cmd, cmdsize, name)
415
- super(raw_data, offset, cmd, cmdsize)
416
- @name = LCStr.new(raw_data, self, name)
504
+ def initialize(view, cmd, cmdsize, name)
505
+ super(view, cmd, cmdsize)
506
+ @name = LCStr.new(self, name)
507
+ end
508
+
509
+ # @param context [MachO::LoadCcommand::SerializationContext] the context
510
+ # @return [String] the serialized fields of the load command
511
+ # @api private
512
+ def serialize(context)
513
+ format = Utils.specialize_format(FORMAT, context.endianness)
514
+ string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :name => name.to_s)
515
+ cmdsize = SIZEOF + string_payload.bytesize
516
+ [cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload
417
517
  end
418
518
  end
419
519
 
@@ -429,13 +529,18 @@ module MachO
429
529
  # @return [Fixnum] a bit vector of linked modules
430
530
  attr_reader :linked_modules
431
531
 
432
- FORMAT = "L=5"
532
+ # @see MachOStructure::FORMAT
533
+ # @api private
534
+ FORMAT = "L=5".freeze
535
+
536
+ # @see MachOStructure::SIZEOF
537
+ # @api private
433
538
  SIZEOF = 20
434
539
 
435
540
  # @api private
436
- def initialize(raw_data, offset, cmd, cmdsize, name, nmodules, linked_modules)
437
- super(raw_data, offset, cmd, cmdsize)
438
- @name = LCStr.new(raw_data, self, name)
541
+ def initialize(view, cmd, cmdsize, name, nmodules, linked_modules)
542
+ super(view, cmd, cmdsize)
543
+ @name = LCStr.new(self, name)
439
544
  @nmodules = nmodules
440
545
  @linked_modules = linked_modules
441
546
  end
@@ -444,7 +549,12 @@ module MachO
444
549
  # A load command used to represent threads.
445
550
  # @note cctools-870 has all fields of thread_command commented out except common ones (cmd, cmdsize)
446
551
  class ThreadCommand < LoadCommand
447
- FORMAT = "L=2"
552
+ # @see MachOStructure::FORMAT
553
+ # @api private
554
+ FORMAT = "L=2".freeze
555
+
556
+ # @see MachOStructure::SIZEOF
557
+ # @api private
448
558
  SIZEOF = 8
449
559
  end
450
560
 
@@ -476,14 +586,18 @@ module MachO
476
586
  # @return [void]
477
587
  attr_reader :reserved6
478
588
 
479
- FORMAT = "L=10"
589
+ # @see MachOStructure::FORMAT
590
+ # @api private
591
+ FORMAT = "L=10".freeze
592
+
593
+ # @see MachOStructure::SIZEOF
594
+ # @api private
480
595
  SIZEOF = 40
481
596
 
482
597
  # @api private
483
- def initialize(raw_data, offset, cmd, cmdsize, init_address, init_module,
484
- reserved1, reserved2, reserved3, reserved4, reserved5,
485
- reserved6)
486
- super(raw_data, offset, cmd, cmdsize)
598
+ def initialize(view, cmd, cmdsize, init_address, init_module, reserved1,
599
+ reserved2, reserved3, reserved4, reserved5, reserved6)
600
+ super(view, cmd, cmdsize)
487
601
  @init_address = init_address
488
602
  @init_module = init_module
489
603
  @reserved1 = reserved1
@@ -523,14 +637,18 @@ module MachO
523
637
  # @return [void]
524
638
  attr_reader :reserved6
525
639
 
526
- FORMAT = "L=2Q=8"
640
+ # @see MachOStructure::FORMAT
641
+ # @api private
642
+ FORMAT = "L=2Q=8".freeze
643
+
644
+ # @see MachOStructure::SIZEOF
645
+ # @api private
527
646
  SIZEOF = 72
528
647
 
529
648
  # @api private
530
- def initialize(raw_data, offset, cmd, cmdsize, init_address, init_module,
531
- reserved1, reserved2, reserved3, reserved4, reserved5,
532
- reserved6)
533
- super(raw_data, offset, cmd, cmdsize)
649
+ def initialize(view, cmd, cmdsize, init_address, init_module, reserved1,
650
+ reserved2, reserved3, reserved4, reserved5, reserved6)
651
+ super(view, cmd, cmdsize)
534
652
  @init_address = init_address
535
653
  @init_module = init_module
536
654
  @reserved1 = reserved1
@@ -548,13 +666,18 @@ module MachO
548
666
  # @return [MachO::LoadCommand::LCStr] the umbrella framework name as an LCStr
549
667
  attr_reader :umbrella
550
668
 
551
- FORMAT = "L=3"
669
+ # @see MachOStructure::FORMAT
670
+ # @api private
671
+ FORMAT = "L=3".freeze
672
+
673
+ # @see MachOStructure::SIZEOF
674
+ # @api private
552
675
  SIZEOF = 12
553
676
 
554
677
  # @api private
555
- def initialize(raw_data, offset, cmd, cmdsize, umbrella)
556
- super(raw_data, offset, cmd, cmdsize)
557
- @umbrella = LCStr.new(raw_data, self, umbrella)
678
+ def initialize(view, cmd, cmdsize, umbrella)
679
+ super(view, cmd, cmdsize)
680
+ @umbrella = LCStr.new(self, umbrella)
558
681
  end
559
682
  end
560
683
 
@@ -564,13 +687,18 @@ module MachO
564
687
  # @return [MachO::LoadCommand::LCStr] the subumbrella framework name as an LCStr
565
688
  attr_reader :sub_umbrella
566
689
 
567
- FORMAT = "L=3"
690
+ # @see MachOStructure::FORMAT
691
+ # @api private
692
+ FORMAT = "L=3".freeze
693
+
694
+ # @see MachOStructure::SIZEOF
695
+ # @api private
568
696
  SIZEOF = 12
569
697
 
570
698
  # @api private
571
- def initialize(raw_data, offset, cmd, cmdsize, sub_umbrella)
572
- super(raw_data, offset, cmd, cmdsize)
573
- @sub_umbrella = LCStr.new(raw_data, self, sub_umbrella)
699
+ def initialize(view, cmd, cmdsize, sub_umbrella)
700
+ super(view, cmd, cmdsize)
701
+ @sub_umbrella = LCStr.new(self, sub_umbrella)
574
702
  end
575
703
  end
576
704
 
@@ -580,13 +708,18 @@ module MachO
580
708
  # @return [MachO::LoadCommand::LCStr] the sublibrary name as an LCStr
581
709
  attr_reader :sub_library
582
710
 
583
- FORMAT = "L=3"
711
+ # @see MachOStructure::FORMAT
712
+ # @api private
713
+ FORMAT = "L=3".freeze
714
+
715
+ # @see MachOStructure::SIZEOF
716
+ # @api private
584
717
  SIZEOF = 12
585
718
 
586
719
  # @api private
587
- def initialize(raw_data, offset, cmd, cmdsize, sub_library)
588
- super(raw_data, offset, cmd, cmdsize)
589
- @sub_library = LCStr.new(raw_data, self, sub_library)
720
+ def initialize(view, cmd, cmdsize, sub_library)
721
+ super(view, cmd, cmdsize)
722
+ @sub_library = LCStr.new(self, sub_library)
590
723
  end
591
724
  end
592
725
 
@@ -596,13 +729,18 @@ module MachO
596
729
  # @return [MachO::LoadCommand::LCStr] the subclient name as an LCStr
597
730
  attr_reader :sub_client
598
731
 
599
- FORMAT = "L=3"
732
+ # @see MachOStructure::FORMAT
733
+ # @api private
734
+ FORMAT = "L=3".freeze
735
+
736
+ # @see MachOStructure::SIZEOF
737
+ # @api private
600
738
  SIZEOF = 12
601
739
 
602
740
  # @api private
603
- def initialize(raw_data, offset, cmd, cmdsize, sub_client)
604
- super(raw_data, offset, cmd, cmdsize)
605
- @sub_client = LCStr.new(raw_data, self, sub_client)
741
+ def initialize(view, cmd, cmdsize, sub_client)
742
+ super(view, cmd, cmdsize)
743
+ @sub_client = LCStr.new(self, sub_client)
606
744
  end
607
745
  end
608
746
 
@@ -621,12 +759,17 @@ module MachO
621
759
  # @return the string table size in bytes
622
760
  attr_reader :strsize
623
761
 
624
- FORMAT = "L=6"
762
+ # @see MachOStructure::FORMAT
763
+ # @api private
764
+ FORMAT = "L=6".freeze
765
+
766
+ # @see MachOStructure::SIZEOF
767
+ # @api private
625
768
  SIZEOF = 24
626
769
 
627
770
  # @api private
628
- def initialize(raw_data, offset, cmd, cmdsize, symoff, nsyms, stroff, strsize)
629
- super(raw_data, offset, cmd, cmdsize)
771
+ def initialize(view, cmd, cmdsize, symoff, nsyms, stroff, strsize)
772
+ super(view, cmd, cmdsize)
630
773
  @symoff = symoff
631
774
  @nsyms = nsyms
632
775
  @stroff = stroff
@@ -691,17 +834,21 @@ module MachO
691
834
  # @return [Fixnum] the number of local relocation entries
692
835
  attr_reader :nlocrel
693
836
 
837
+ # @see MachOStructure::FORMAT
838
+ # @api private
839
+ FORMAT = "L=20".freeze
694
840
 
695
- FORMAT = "L=20"
841
+ # @see MachOStructure::SIZEOF
842
+ # @api private
696
843
  SIZEOF = 80
697
844
 
698
845
  # ugh
699
846
  # @api private
700
- def initialize(raw_data, offset, cmd, cmdsize, ilocalsym, nlocalsym, iextdefsym,
701
- nextdefsym, iundefsym, nundefsym, tocoff, ntoc, modtaboff,
702
- nmodtab, extrefsymoff, nextrefsyms, indirectsymoff,
703
- nindirectsyms, extreloff, nextrel, locreloff, nlocrel)
704
- super(raw_data, offset, cmd, cmdsize)
847
+ def initialize(view, cmd, cmdsize, ilocalsym, nlocalsym, iextdefsym,
848
+ nextdefsym, iundefsym, nundefsym, tocoff, ntoc, modtaboff,
849
+ nmodtab, extrefsymoff, nextrefsyms, indirectsymoff,
850
+ nindirectsyms, extreloff, nextrel, locreloff, nlocrel)
851
+ super(view, cmd, cmdsize)
705
852
  @ilocalsym = ilocalsym
706
853
  @nlocalsym = nlocalsym
707
854
  @iextdefsym = iextdefsym
@@ -732,14 +879,58 @@ module MachO
732
879
  # @return [Fixnum] the number of hints in the hint table
733
880
  attr_reader :nhints
734
881
 
735
- FORMAT = "L=4"
882
+ # @return [MachO::TwolevelHintsCommand::TwolevelHintTable] the hint table
883
+ attr_reader :table
884
+
885
+ # @see MachOStructure::FORMAT
886
+ # @api private
887
+ FORMAT = "L=4".freeze
888
+
889
+ # @see MachOStructure::SIZEOF
890
+ # @api private
736
891
  SIZEOF = 16
737
892
 
738
893
  # @api private
739
- def initialize(raw_data, offset, cmd, cmdsize, htoffset, nhints)
740
- super(raw_data, offset, cmd, cmdsize)
894
+ def initialize(view, cmd, cmdsize, htoffset, nhints)
895
+ super(view, cmd, cmdsize)
741
896
  @htoffset = htoffset
742
897
  @nhints = nhints
898
+ @table = TwolevelHintsTable.new(view, htoffset, nhints)
899
+ end
900
+
901
+ # A representation of the two-level namespace lookup hints table exposed
902
+ # by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`).
903
+ class TwolevelHintsTable
904
+ # @return [Array<MachO::TwoLevelHintsTable::TwoLevelHint>] all hints in the table
905
+ attr_reader :hints
906
+
907
+ # @param view [MachO::MachOView] the view into the current Mach-O
908
+ # @param htoffset [Fixnum] the offset of the hints table
909
+ # @param nhints [Fixnum] the number of two-level hints in the table
910
+ # @api private
911
+ def initialize(view, htoffset, nhints)
912
+ format = Utils.specialize_format("L=#{nhints}", view.endianness)
913
+ raw_table = view.raw_data[htoffset, nhints * 4]
914
+ blobs = raw_table.unpack(format)
915
+
916
+ @hints = blobs.map { |b| TwolevelHint.new(b) }
917
+ end
918
+
919
+ # An individual two-level namespace lookup hint.
920
+ class TwolevelHint
921
+ # @return [Fixnum] the index into the sub-images
922
+ attr_reader :isub_image
923
+
924
+ # @return [Fixnum] the index into the table of contents
925
+ attr_reader :itoc
926
+
927
+ # @param blob [Fixnum] the 32-bit number containing the lookup hint
928
+ # @api private
929
+ def initialize(blob)
930
+ @isub_image = blob >> 24
931
+ @itoc = blob & 0x00FFFFFF
932
+ end
933
+ end
743
934
  end
744
935
  end
745
936
 
@@ -749,12 +940,17 @@ module MachO
749
940
  # @return [Fixnum] the checksum or 0
750
941
  attr_reader :cksum
751
942
 
752
- FORMAT = "L=3"
943
+ # @see MachOStructure::FORMAT
944
+ # @api private
945
+ FORMAT = "L=3".freeze
946
+
947
+ # @see MachOStructure::SIZEOF
948
+ # @api private
753
949
  SIZEOF = 12
754
950
 
755
951
  # @api private
756
- def initialize(raw_data, offset, cmd, cmdsize, cksum)
757
- super(raw_data, offset, cmd, cmdsize)
952
+ def initialize(view, cmd, cmdsize, cksum)
953
+ super(view, cmd, cmdsize)
758
954
  @cksum = cksum
759
955
  end
760
956
  end
@@ -766,13 +962,28 @@ module MachO
766
962
  # @return [MachO::LoadCommand::LCStr] the path to add to the run path as an LCStr
767
963
  attr_reader :path
768
964
 
769
- FORMAT = "L=3"
965
+ # @see MachOStructure::FORMAT
966
+ # @api private
967
+ FORMAT = "L=3".freeze
968
+
969
+ # @see MachOStructure::SIZEOF
970
+ # @api private
770
971
  SIZEOF = 12
771
972
 
772
973
  # @api private
773
- def initialize(raw_data, offset, cmd, cmdsize, path)
774
- super(raw_data, offset, cmd, cmdsize)
775
- @path = LCStr.new(raw_data, self, path)
974
+ def initialize(view, cmd, cmdsize, path)
975
+ super(view, cmd, cmdsize)
976
+ @path = LCStr.new(self, path)
977
+ end
978
+
979
+ # @param context [MachO::LoadCcommand::SerializationContext] the context
980
+ # @return [String] the serialized fields of the load command
981
+ # @api private
982
+ def serialize(context)
983
+ format = Utils.specialize_format(FORMAT, context.endianness)
984
+ string_payload, string_offsets = Utils.pack_strings(SIZEOF, context.alignment, :path => path.to_s)
985
+ cmdsize = SIZEOF + string_payload.bytesize
986
+ [cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload
776
987
  end
777
988
  end
778
989
 
@@ -786,12 +997,17 @@ module MachO
786
997
  # @return [Fixnum] size of the data in the __LINKEDIT segment
787
998
  attr_reader :datasize
788
999
 
789
- FORMAT = "L=4"
1000
+ # @see MachOStructure::FORMAT
1001
+ # @api private
1002
+ FORMAT = "L=4".freeze
1003
+
1004
+ # @see MachOStructure::SIZEOF
1005
+ # @api private
790
1006
  SIZEOF = 16
791
1007
 
792
1008
  # @api private
793
- def initialize(raw_data, offset, cmd, cmdsize, dataoff, datasize)
794
- super(raw_data, offset, cmd, cmdsize)
1009
+ def initialize(view, cmd, cmdsize, dataoff, datasize)
1010
+ super(view, cmd, cmdsize)
795
1011
  @dataoff = dataoff
796
1012
  @datasize = datasize
797
1013
  end
@@ -809,12 +1025,17 @@ module MachO
809
1025
  # @return [Fixnum] the encryption system, or 0 if not encrypted yet
810
1026
  attr_reader :cryptid
811
1027
 
812
- FORMAT = "L=5"
1028
+ # @see MachOStructure::FORMAT
1029
+ # @api private
1030
+ FORMAT = "L=5".freeze
1031
+
1032
+ # @see MachOStructure::SIZEOF
1033
+ # @api private
813
1034
  SIZEOF = 20
814
1035
 
815
1036
  # @api private
816
- def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid)
817
- super(raw_data, offset, cmd, cmdsize)
1037
+ def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid)
1038
+ super(view, cmd, cmdsize)
818
1039
  @cryptoff = cryptoff
819
1040
  @cryptsize = cryptsize
820
1041
  @cryptid = cryptid
@@ -836,12 +1057,17 @@ module MachO
836
1057
  # @return [Fixnum] 64-bit padding value
837
1058
  attr_reader :pad
838
1059
 
839
- FORMAT = "L=6"
1060
+ # @see MachOStructure::FORMAT
1061
+ # @api private
1062
+ FORMAT = "L=6".freeze
1063
+
1064
+ # @see MachOStructure::SIZEOF
1065
+ # @api private
840
1066
  SIZEOF = 24
841
1067
 
842
1068
  # @api private
843
- def initialize(raw_data, offset, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
844
- super(raw_data, offset, cmd, cmdsize)
1069
+ def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
1070
+ super(view, cmd, cmdsize)
845
1071
  @cryptoff = cryptoff
846
1072
  @cryptsize = cryptsize
847
1073
  @cryptid = cryptid
@@ -858,12 +1084,17 @@ module MachO
858
1084
  # @return [Fixnum] the SDK version X.Y.Z packed as x16.y8.z8
859
1085
  attr_reader :sdk
860
1086
 
861
- FORMAT = "L=4"
1087
+ # @see MachOStructure::FORMAT
1088
+ # @api private
1089
+ FORMAT = "L=4".freeze
1090
+
1091
+ # @see MachOStructure::SIZEOF
1092
+ # @api private
862
1093
  SIZEOF = 16
863
1094
 
864
1095
  # @api private
865
- def initialize(raw_data, offset, cmd, cmdsize, version, sdk)
866
- super(raw_data, offset, cmd, cmdsize)
1096
+ def initialize(view, cmd, cmdsize, version, sdk)
1097
+ super(view, cmd, cmdsize)
867
1098
  @version = version
868
1099
  @sdk = sdk
869
1100
  end
@@ -925,14 +1156,19 @@ module MachO
925
1156
  # @return [Fixnum] the size of the export information
926
1157
  attr_reader :export_size
927
1158
 
928
- FORMAT = "L=12"
1159
+ # @see MachOStructure::FORMAT
1160
+ # @api private
1161
+ FORMAT = "L=12".freeze
1162
+
1163
+ # @see MachOStructure::SIZEOF
1164
+ # @api private
929
1165
  SIZEOF = 48
930
1166
 
931
1167
  # @api private
932
- def initialize(raw_data, offset, cmd, cmdsize, rebase_off, rebase_size, bind_off,
933
- bind_size, weak_bind_off, weak_bind_size, lazy_bind_off,
934
- lazy_bind_size, export_off, export_size)
935
- super(raw_data, offset, cmd, cmdsize)
1168
+ def initialize(view, cmd, cmdsize, rebase_off, rebase_size, bind_off,
1169
+ bind_size, weak_bind_off, weak_bind_size, lazy_bind_off,
1170
+ lazy_bind_size, export_off, export_size)
1171
+ super(view, cmd, cmdsize)
936
1172
  @rebase_off = rebase_off
937
1173
  @rebase_size = rebase_size
938
1174
  @bind_off = bind_off
@@ -952,12 +1188,17 @@ module MachO
952
1188
  # @return [Fixnum] the number of strings
953
1189
  attr_reader :count
954
1190
 
955
- FORMAT = "L=3"
1191
+ # @see MachOStructure::FORMAT
1192
+ # @api private
1193
+ FORMAT = "L=3".freeze
1194
+
1195
+ # @see MachOStructure::SIZEOF
1196
+ # @api private
956
1197
  SIZEOF = 12
957
1198
 
958
1199
  # @api private
959
- def initialize(raw_data, offset, cmd, cmdsize, count)
960
- super(raw_data, offset, cmd, cmdsize)
1200
+ def initialize(view, cmd, cmdsize, count)
1201
+ super(view, cmd, cmdsize)
961
1202
  @count = count
962
1203
  end
963
1204
  end
@@ -970,12 +1211,17 @@ module MachO
970
1211
  # @return [Fixnum] if not 0, the initial stack size.
971
1212
  attr_reader :stacksize
972
1213
 
973
- FORMAT = "L=2Q=2"
1214
+ # @see MachOStructure::FORMAT
1215
+ # @api private
1216
+ FORMAT = "L=2Q=2".freeze
1217
+
1218
+ # @see MachOStructure::SIZEOF
1219
+ # @api private
974
1220
  SIZEOF = 24
975
1221
 
976
1222
  # @api private
977
- def initialize(raw_data, offset, cmd, cmdsize, entryoff, stacksize)
978
- super(raw_data, offset, cmd, cmdsize)
1223
+ def initialize(view, cmd, cmdsize, entryoff, stacksize)
1224
+ super(view, cmd, cmdsize)
979
1225
  @entryoff = entryoff
980
1226
  @stacksize = stacksize
981
1227
  end
@@ -987,12 +1233,17 @@ module MachO
987
1233
  # @return [Fixnum] the version packed as a24.b10.c10.d10.e10
988
1234
  attr_reader :version
989
1235
 
990
- FORMAT = "L=2Q=1"
1236
+ # @see MachOStructure::FORMAT
1237
+ # @api private
1238
+ FORMAT = "L=2Q=1".freeze
1239
+
1240
+ # @see MachOStructure::SIZEOF
1241
+ # @api private
991
1242
  SIZEOF = 16
992
1243
 
993
1244
  # @api private
994
- def initialize(raw_data, offset, cmd, cmdsize, version)
995
- super(raw_data, offset, cmd, cmdsize)
1245
+ def initialize(view, cmd, cmdsize, version)
1246
+ super(view, cmd, cmdsize)
996
1247
  @version = version
997
1248
  end
998
1249
 
@@ -1008,4 +1259,94 @@ module MachO
1008
1259
  segs.join(".")
1009
1260
  end
1010
1261
  end
1262
+
1263
+ # An obsolete load command containing the offset and size of the (GNU style)
1264
+ # symbol table information. Corresponds to LC_SYMSEG.
1265
+ class SymsegCommand < LoadCommand
1266
+ # @return [Fixnum] the offset to the symbol segment
1267
+ attr_reader :offset
1268
+
1269
+ # @return [Fixnum] the size of the symbol segment in bytes
1270
+ attr_reader :size
1271
+
1272
+ # @see MachOStructure::FORMAT
1273
+ # @api private
1274
+ FORMAT = "L=4".freeze
1275
+
1276
+ # @see MachOStructure::SIZEOF
1277
+ # @api private
1278
+ SIZEOF = 16
1279
+
1280
+ # @api private
1281
+ def initialize(view, cmd, cmdsize, offset, size)
1282
+ super(view, cmd, cmdsize)
1283
+ @offset = offset
1284
+ @size = size
1285
+ end
1286
+ end
1287
+
1288
+ # An obsolete load command containing a free format string table. Each string
1289
+ # is null-terminated and the command is zero-padded to a multiple of 4.
1290
+ # Corresponds to LC_IDENT.
1291
+ class IdentCommand < LoadCommand
1292
+ # @see MachOStructure::FORMAT
1293
+ # @api private
1294
+ FORMAT = "L=2".freeze
1295
+
1296
+ # @see MachOStructure::SIZEOF
1297
+ # @api private
1298
+ SIZEOF = 8
1299
+ end
1300
+
1301
+ # An obsolete load command containing the path to a file to be loaded into
1302
+ # memory. Corresponds to LC_FVMFILE.
1303
+ class FvmfileCommand < LoadCommand
1304
+ # @return [MachO::LoadCommand::LCStr] the pathname of the file being loaded
1305
+ attr_reader :name
1306
+
1307
+ # @return [Fixnum] the virtual address being loaded at
1308
+ attr_reader :header_addr
1309
+
1310
+ # @see MachOStructure::FORMAT
1311
+ # @api private
1312
+ FORMAT = "L=4".freeze
1313
+
1314
+ # @see MachOStructure::SIZEOF
1315
+ # @api private
1316
+ SIZEOF = 16
1317
+
1318
+ def initialize(view, cmd, cmdsize, name, header_addr)
1319
+ super(view, cmd, cmdsize)
1320
+ @name = LCStr.new(self, name)
1321
+ @header_addr = header_addr
1322
+ end
1323
+ end
1324
+
1325
+ # An obsolete load command containing the path to a library to be loaded into
1326
+ # memory. Corresponds to LC_LOADFVMLIB and LC_IDFVMLIB.
1327
+ class FvmlibCommand < LoadCommand
1328
+ # @return [MachO::LoadCommand::LCStr] the library's target pathname
1329
+ attr_reader :name
1330
+
1331
+ # @return [Fixnum] the library's minor version number
1332
+ attr_reader :minor_version
1333
+
1334
+ # @return [Fixnum] the library's header address
1335
+ attr_reader :header_addr
1336
+
1337
+ # @see MachOStructure::FORMAT
1338
+ # @api private
1339
+ FORMAT = "L=5".freeze
1340
+
1341
+ # @see MachOStructure::SIZEOF
1342
+ # @api private
1343
+ SIZEOF = 20
1344
+
1345
+ def initialize(view, cmd, cmdsize, name, minor_version, header_addr)
1346
+ super(view, cmd, cmdsize)
1347
+ @name = LCStr.new(self, name)
1348
+ @minor_version = minor_version
1349
+ @header_addr = header_addr
1350
+ end
1351
+ end
1011
1352
  end