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.
@@ -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