ruby-macho 2.0.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6341c1a29aeae7169fea7f1e7fc11339331f21c2b07a3a19e7f68b5dd76af03d
4
- data.tar.gz: 9b5dd29c4a03c18bf6261e20641c95c584958ee142d4d64a37f9db8352065727
3
+ metadata.gz: cd8907fa4f63922522d0e84d6b5cdcc2a0c36f423db72bd0f947d328935c63de
4
+ data.tar.gz: b7f2f638a051121f3f5e3f5507c8e69be56ec25239df2a435a9cfebf08834619
5
5
  SHA512:
6
- metadata.gz: c3964af02f0b1abf51de625e1d69a8fd966c30d5bf93cfde410ac127fab57864ae6331441d2afa1acdd100c7187f5d05883ecd8736d88076507823b16627ab10
7
- data.tar.gz: c5a8ec50db69dccef0161dd96d6b7f23a47713a0fb8551d1bae10afa2690ed6837b52f9f3f49bd0826610b7c7a1841fadda2d1c53f6ab91f118732e36a3a4bd7
6
+ metadata.gz: 06bec5c6b0c5336d87298f85ffa7e9bcf09ef092f18fba36b3ee5b6c6469b2e0ed9cd02732808eef9b8cae9095b348ed2eb9240a531200055fed615ca3cdf149
7
+ data.tar.gz: 51b382c46e95a60f416cfe304bbd7a95169b5f6dbc0709a6ef3ce7bc6ede8d29d3ea30b2032ffb73357bbb2a7df0f21a23f553f70c0fbaf9a37c538d7c98158b
data/README.md CHANGED
@@ -13,6 +13,14 @@ The [Mach-O file format](https://en.wikipedia.org/wiki/Mach-O) is used by macOS
13
13
  and iOS (among others) as a general purpose binary format for object files,
14
14
  executables, dynamic libraries, and so forth.
15
15
 
16
+ ### Installation
17
+
18
+ ruby-macho can be installed via RubyGems:
19
+
20
+ ```bash
21
+ $ gem install ruby-macho
22
+ ```
23
+
16
24
  ### Documentation
17
25
 
18
26
  Full documentation is available on [RubyDoc](http://www.rubydoc.info/gems/ruby-macho/).
@@ -46,7 +54,6 @@ puts lc_vers.version_string # => "10.10.0"
46
54
 
47
55
  ### What needs to be done?
48
56
 
49
- * Documentation.
50
57
  * Unit and performance testing.
51
58
 
52
59
  Attribution:
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
1
5
  require_relative "macho/structure"
2
6
  require_relative "macho/view"
3
7
  require_relative "macho/headers"
@@ -12,7 +16,7 @@ require_relative "macho/tools"
12
16
  # The primary namespace for ruby-macho.
13
17
  module MachO
14
18
  # release version
15
- VERSION = "2.0.0".freeze
19
+ VERSION = "2.5.0"
16
20
 
17
21
  # Opens the given filename as a MachOFile or FatFile, depending on its magic.
18
22
  # @param filename [String] the file being opened
@@ -25,7 +29,7 @@ module MachO
25
29
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
26
30
  raise TruncatedFileError unless File.stat(filename).size >= 4
27
31
 
28
- magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first
32
+ magic = File.open(filename, "rb") { |f| f.read(4) }.unpack1("N")
29
33
 
30
34
  if Utils.fat_magic?(magic)
31
35
  file = FatFile.new(filename)
@@ -37,4 +41,21 @@ module MachO
37
41
 
38
42
  file
39
43
  end
44
+
45
+ # Signs the dylib using an ad-hoc identity.
46
+ # Necessary after making any changes to a dylib, since otherwise
47
+ # changing a signed file invalidates its signature.
48
+ # @param filename [String] the file being opened
49
+ # @return [void]
50
+ # @raise [ModificationError] if the operation fails
51
+ def self.codesign!(filename)
52
+ raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
53
+ raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
54
+
55
+ _, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
56
+ "--preserve-metadata=entitlements,requirements,flags,runtime",
57
+ filename)
58
+
59
+ raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
60
+ end
40
61
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A generic Mach-O error in execution.
3
5
  class MachOError < RuntimeError
@@ -7,6 +9,11 @@ module MachO
7
9
  class ModificationError < MachOError
8
10
  end
9
11
 
12
+ # Raised when codesigning fails. Certain environments
13
+ # may want to rescue this to treat it as non-fatal.
14
+ class CodeSigningError < MachOError
15
+ end
16
+
10
17
  # Raised when a Mach-O file modification fails but can be recovered when
11
18
  # operating on multiple Mach-O slices of a fat binary in non-strict mode.
12
19
  class RecoverableModificationError < ModificationError
@@ -25,10 +32,6 @@ module MachO
25
32
 
26
33
  # Raised when a file is not a Mach-O.
27
34
  class NotAMachOError < MachOError
28
- # @param error [String] the error in question
29
- def initialize(error)
30
- super error
31
- end
32
35
  end
33
36
 
34
37
  # Raised when a file is too short to be a valid Mach-O file.
@@ -41,8 +44,8 @@ module MachO
41
44
  # Raised when a file's magic bytes are not valid Mach-O magic.
42
45
  class MagicError < NotAMachOError
43
46
  # @param num [Integer] the unknown number
44
- def initialize(num)
45
- super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
47
+ def initialize(magic)
48
+ super "Unrecognized Mach-O magic: 0x%02<magic>x" % { :magic => magic }
46
49
  end
47
50
  end
48
51
 
@@ -71,7 +74,7 @@ module MachO
71
74
  class CPUTypeError < MachOError
72
75
  # @param cputype [Integer] the unknown CPU type
73
76
  def initialize(cputype)
74
- super "Unrecognized CPU type: 0x#{"%08x" % cputype}"
77
+ super "Unrecognized CPU type: 0x%08<cputype>x" % { :cputype => cputype }
75
78
  end
76
79
  end
77
80
 
@@ -80,8 +83,8 @@ module MachO
80
83
  # @param cputype [Integer] the CPU type of the unknown pair
81
84
  # @param cpusubtype [Integer] the CPU sub-type of the unknown pair
82
85
  def initialize(cputype, cpusubtype)
83
- super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype}" \
84
- " (for CPU type: 0x#{"%08x" % cputype})"
86
+ super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
87
+ " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
85
88
  end
86
89
  end
87
90
 
@@ -89,7 +92,7 @@ module MachO
89
92
  class FiletypeError < MachOError
90
93
  # @param num [Integer] the unknown number
91
94
  def initialize(num)
92
- super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
95
+ super "Unrecognized Mach-O filetype code: 0x%02<num>x" % { :num => num }
93
96
  end
94
97
  end
95
98
 
@@ -97,7 +100,7 @@ module MachO
97
100
  class LoadCommandError < MachOError
98
101
  # @param num [Integer] the unknown number
99
102
  def initialize(num)
100
- super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
103
+ super "Unrecognized Mach-O load command: 0x%02<num>x" % { :num => num }
101
104
  end
102
105
  end
103
106
 
@@ -194,4 +197,14 @@ module MachO
194
197
  super "Unimplemented: #{thing}"
195
198
  end
196
199
  end
200
+
201
+ # Raised when attempting to create a {FatFile} from one or more {MachOFile}s
202
+ # whose offsets will not fit within the resulting 32-bit {Headers::FatArch#offset} fields.
203
+ class FatArchOffsetOverflowError < MachOError
204
+ # @param offset [Integer] the offending offset
205
+ def initialize(offset)
206
+ super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset." \
207
+ " Consider merging with `fat64: true`"
208
+ end
209
+ end
197
210
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module MachO
@@ -11,10 +13,14 @@ module MachO
11
13
  # @return [String] the filename loaded from, or nil if loaded from a binary string
12
14
  attr_accessor :filename
13
15
 
16
+ # @return [Hash] any parser options that the instance was created with
17
+ # @note Options specified in a {FatFile} trickle down into the internal {MachOFile}s.
18
+ attr_reader :options
19
+
14
20
  # @return [Headers::FatHeader] the file's header
15
21
  attr_reader :header
16
22
 
17
- # @return [Array<Headers::FatArch>] an array of fat architectures
23
+ # @return [Array<Headers::FatArch>, Array<Headers::FatArch64] an array of fat architectures
18
24
  attr_reader :fat_archs
19
25
 
20
26
  # @return [Array<MachOFile>] an array of Mach-O binaries
@@ -22,37 +28,47 @@ module MachO
22
28
 
23
29
  # Creates a new FatFile from the given (single-arch) Mach-Os
24
30
  # @param machos [Array<MachOFile>] the machos to combine
31
+ # @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
25
32
  # @return [FatFile] a new FatFile containing the give machos
26
33
  # @raise [ArgumentError] if less than one Mach-O is given
27
- def self.new_from_machos(*machos)
34
+ # @raise [FatArchOffsetOverflowError] if the Mach-Os are too big to be represented
35
+ # in a 32-bit {Headers::FatArch} and `fat64` is `false`.
36
+ def self.new_from_machos(*machos, fat64: false)
28
37
  raise ArgumentError, "expected at least one Mach-O" if machos.empty?
29
38
 
39
+ fa_klass, magic = if fat64
40
+ [Headers::FatArch64, Headers::FAT_MAGIC_64]
41
+ else
42
+ [Headers::FatArch, Headers::FAT_MAGIC]
43
+ end
44
+
30
45
  # put the smaller alignments further forwards in fat macho, so that we do less padding
31
46
  machos = machos.sort_by(&:segment_alignment)
32
47
 
33
48
  bin = +""
34
49
 
35
- bin << Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size).serialize
36
- offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
50
+ bin << Headers::FatHeader.new(magic, machos.size).serialize
51
+ offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)
37
52
 
38
53
  macho_pads = {}
39
- macho_bins = {}
40
54
 
41
55
  machos.each do |macho|
42
- macho_offset = Utils.round(offset, 2**macho.segment_alignment)
56
+ macho_offset = Utils.round(offset, 2**macho.segment_alignment)
57
+
58
+ raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
59
+
43
60
  macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
44
- macho_bins[macho] = macho.serialize
45
61
 
46
- bin << Headers::FatArch.new(macho.header.cputype, macho.header.cpusubtype,
47
- macho_offset, macho_bins[macho].bytesize,
48
- macho.segment_alignment).serialize
62
+ bin << fa_klass.new(macho.header.cputype, macho.header.cpusubtype,
63
+ macho_offset, macho.serialize.bytesize,
64
+ macho.segment_alignment).serialize
49
65
 
50
- offset += (macho_bins[macho].bytesize + macho_pads[macho])
66
+ offset += (macho.serialize.bytesize + macho_pads[macho])
51
67
  end
52
68
 
53
- machos.each do |macho|
69
+ machos.each do |macho| # rubocop:disable Style/CombinableLoops
54
70
  bin << Utils.nullpad(macho_pads[macho])
55
- bin << macho_bins[macho]
71
+ bin << macho.serialize
56
72
  end
57
73
 
58
74
  new_from_bin(bin)
@@ -60,30 +76,36 @@ module MachO
60
76
 
61
77
  # Creates a new FatFile instance from a binary string.
62
78
  # @param bin [String] a binary string containing raw Mach-O data
79
+ # @param opts [Hash] options to control the parser with
80
+ # @note see {MachOFile#initialize} for currently valid options
63
81
  # @return [FatFile] a new FatFile
64
- def self.new_from_bin(bin)
82
+ def self.new_from_bin(bin, **opts)
65
83
  instance = allocate
66
- instance.initialize_from_bin(bin)
84
+ instance.initialize_from_bin(bin, opts)
67
85
 
68
86
  instance
69
87
  end
70
88
 
71
89
  # Creates a new FatFile from the given filename.
72
90
  # @param filename [String] the fat file to load from
91
+ # @param opts [Hash] options to control the parser with
92
+ # @note see {MachOFile#initialize} for currently valid options
73
93
  # @raise [ArgumentError] if the given file does not exist
74
- def initialize(filename)
94
+ def initialize(filename, **opts)
75
95
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
76
96
 
77
97
  @filename = filename
98
+ @options = opts
78
99
  @raw_data = File.open(@filename, "rb", &:read)
79
100
  populate_fields
80
101
  end
81
102
 
82
- # Initializes a new FatFile instance from a binary string.
103
+ # Initializes a new FatFile instance from a binary string with the given options.
83
104
  # @see new_from_bin
84
105
  # @api private
85
- def initialize_from_bin(bin)
106
+ def initialize_from_bin(bin, opts)
86
107
  @filename = nil
108
+ @options = opts
87
109
  @raw_data = bin
88
110
  populate_fields
89
111
  end
@@ -278,6 +300,7 @@ module MachO
278
300
  # @note Overwrites all data in the file!
279
301
  def write!
280
302
  raise MachOError, "no initial file to write to" if filename.nil?
303
+
281
304
  File.open(@filename, "wb") { |f| f.write(@raw_data) }
282
305
  end
283
306
 
@@ -327,10 +350,12 @@ module MachO
327
350
  def populate_fat_archs
328
351
  archs = []
329
352
 
330
- fa_off = Headers::FatHeader.bytesize
331
- fa_len = Headers::FatArch.bytesize
353
+ fa_klass = Utils.fat_magic32?(header.magic) ? Headers::FatArch : Headers::FatArch64
354
+ fa_off = Headers::FatHeader.bytesize
355
+ fa_len = fa_klass.bytesize
356
+
332
357
  header.nfat_arch.times do |i|
333
- archs << Headers::FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
358
+ archs << fa_klass.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
334
359
  end
335
360
 
336
361
  archs
@@ -343,7 +368,7 @@ module MachO
343
368
  machos = []
344
369
 
345
370
  fat_archs.each do |arch|
346
- machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
371
+ machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size], **options)
347
372
  end
348
373
 
349
374
  machos
@@ -373,15 +398,14 @@ module MachO
373
398
  errors = []
374
399
 
375
400
  machos.each_with_index do |macho, index|
376
- begin
377
- yield macho
378
- rescue RecoverableModificationError => error
379
- error.macho_slice = index
380
-
381
- # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
382
- raise error if strict
383
- errors << error
384
- end
401
+ yield macho
402
+ rescue RecoverableModificationError => e
403
+ e.macho_slice = index
404
+
405
+ # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
406
+ raise e if strict
407
+
408
+ errors << e
385
409
  end
386
410
 
387
411
  # Non-strict mode: Raise first error if *all* Mach-O slices failed.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing the headers of Mach-O binaries.
3
5
  module Headers
@@ -6,11 +8,19 @@ module MachO
6
8
  FAT_MAGIC = 0xcafebabe
7
9
 
8
10
  # little-endian fat magic
9
- # this is defined, but should never appear in ruby-macho code because
10
- # fat headers are always big-endian and therefore always unpacked as such.
11
+ # @note This is defined for completeness, but should never appear in ruby-macho code,
12
+ # since fat headers are always big-endian.
11
13
  # @api private
12
14
  FAT_CIGAM = 0xbebafeca
13
15
 
16
+ # 64-bit big-endian fat magic
17
+ FAT_MAGIC_64 = 0xcafebabf
18
+
19
+ # 64-bit little-endian fat magic
20
+ # @note This is defined for completeness, but should never appear in ruby-macho code,
21
+ # since fat headers are always big-endian.
22
+ FAT_CIGAM_64 = 0xbfbafeca
23
+
14
24
  # 32-bit big-endian magic
15
25
  # @api private
16
26
  MH_MAGIC = 0xfeedface
@@ -31,6 +41,7 @@ module MachO
31
41
  # @api private
32
42
  MH_MAGICS = {
33
43
  FAT_MAGIC => "FAT_MAGIC",
44
+ FAT_MAGIC_64 => "FAT_MAGIC_64",
34
45
  MH_MAGIC => "MH_MAGIC",
35
46
  MH_CIGAM => "MH_CIGAM",
36
47
  MH_MAGIC_64 => "MH_MAGIC_64",
@@ -41,6 +52,11 @@ module MachO
41
52
  # @api private
42
53
  CPU_ARCH_ABI64 = 0x01000000
43
54
 
55
+ # mask for CPUs with 64-bit architectures (when running a 32-bit ABI?)
56
+ # @see https://github.com/Homebrew/ruby-macho/issues/113
57
+ # @api private
58
+ CPU_ARCH_ABI32 = 0x02000000
59
+
44
60
  # any CPU (unused?)
45
61
  # @api private
46
62
  CPU_TYPE_ANY = -1
@@ -69,6 +85,10 @@ module MachO
69
85
  # @api private
70
86
  CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64)
71
87
 
88
+ # 64-bit ARM compatible CPUs (running in 32-bit mode?)
89
+ # @see https://github.com/Homebrew/ruby-macho/issues/113
90
+ CPU_TYPE_ARM64_32 = (CPU_TYPE_ARM | CPU_ARCH_ABI32)
91
+
72
92
  # PowerPC compatible CPUs
73
93
  # @api private
74
94
  CPU_TYPE_POWERPC = 0x12
@@ -85,6 +105,7 @@ module MachO
85
105
  CPU_TYPE_X86_64 => :x86_64,
86
106
  CPU_TYPE_ARM => :arm,
87
107
  CPU_TYPE_ARM64 => :arm64,
108
+ CPU_TYPE_ARM64_32 => :arm64_32,
88
109
  CPU_TYPE_POWERPC => :ppc,
89
110
  CPU_TYPE_POWERPC64 => :ppc64,
90
111
  }.freeze
@@ -218,6 +239,14 @@ module MachO
218
239
  # @api private
219
240
  CPU_SUBTYPE_ARM64_V8 = 1
220
241
 
242
+ # the v8 sub-type for `CPU_TYPE_ARM64_32`
243
+ # @api private
244
+ CPU_SUBTYPE_ARM64_32_V8 = 1
245
+
246
+ # the e (A12) sub-type for `CPU_TYPE_ARM64`
247
+ # @api private
248
+ CPU_SUBTYPE_ARM64E = 2
249
+
221
250
  # the lowest common sub-type for `CPU_TYPE_MC88000`
222
251
  # @api private
223
252
  CPU_SUBTYPE_MC88000_ALL = 0
@@ -327,6 +356,10 @@ module MachO
327
356
  CPU_TYPE_ARM64 => {
328
357
  CPU_SUBTYPE_ARM64_ALL => :arm64,
329
358
  CPU_SUBTYPE_ARM64_V8 => :arm64v8,
359
+ CPU_SUBTYPE_ARM64E => :arm64e,
360
+ }.freeze,
361
+ CPU_TYPE_ARM64_32 => {
362
+ CPU_SUBTYPE_ARM64_32_V8 => :arm64_32v8,
330
363
  }.freeze,
331
364
  CPU_TYPE_POWERPC => {
332
365
  CPU_SUBTYPE_POWERPC_ALL => :ppc,
@@ -459,7 +492,7 @@ module MachO
459
492
  # always big-endian
460
493
  # @see MachOStructure::FORMAT
461
494
  # @api private
462
- FORMAT = "N2".freeze
495
+ FORMAT = "N2"
463
496
 
464
497
  # @see MachOStructure::SIZEOF
465
498
  # @api private
@@ -467,6 +500,7 @@ module MachO
467
500
 
468
501
  # @api private
469
502
  def initialize(magic, nfat_arch)
503
+ super()
470
504
  @magic = magic
471
505
  @nfat_arch = nfat_arch
472
506
  end
@@ -486,8 +520,10 @@ module MachO
486
520
  end
487
521
  end
488
522
 
489
- # Fat binary header architecture structure. A Fat binary has one or more of
490
- # these, representing one or more internal Mach-O blobs.
523
+ # 32-bit fat binary header architecture structure. A 32-bit fat Mach-O has one or more of
524
+ # these, indicating one or more internal Mach-O blobs.
525
+ # @note "32-bit" indicates the fact that this structure stores 32-bit offsets, not that the
526
+ # Mach-Os that it points to necessarily *are* 32-bit.
491
527
  # @see MachO::Headers::FatHeader
492
528
  class FatArch < MachOStructure
493
529
  # @return [Integer] the CPU type of the Mach-O
@@ -505,10 +541,10 @@ module MachO
505
541
  # @return [Integer] the alignment, as a power of 2
506
542
  attr_reader :align
507
543
 
508
- # always big-endian
544
+ # @note Always big endian.
509
545
  # @see MachOStructure::FORMAT
510
546
  # @api private
511
- FORMAT = "N5".freeze
547
+ FORMAT = "L>5"
512
548
 
513
549
  # @see MachOStructure::SIZEOF
514
550
  # @api private
@@ -516,6 +552,7 @@ module MachO
516
552
 
517
553
  # @api private
518
554
  def initialize(cputype, cpusubtype, offset, size, align)
555
+ super()
519
556
  @cputype = cputype
520
557
  @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
521
558
  @offset = offset
@@ -542,6 +579,43 @@ module MachO
542
579
  end
543
580
  end
544
581
 
582
+ # 64-bit fat binary header architecture structure. A 64-bit fat Mach-O has one or more of
583
+ # these, indicating one or more internal Mach-O blobs.
584
+ # @note "64-bit" indicates the fact that this structure stores 64-bit offsets, not that the
585
+ # Mach-Os that it points to necessarily *are* 64-bit.
586
+ # @see MachO::Headers::FatHeader
587
+ class FatArch64 < FatArch
588
+ # @return [void]
589
+ attr_reader :reserved
590
+
591
+ # @note Always big endian.
592
+ # @see MachOStructure::FORMAT
593
+ # @api private
594
+ FORMAT = "L>2Q>2L>2"
595
+
596
+ # @see MachOStructure::SIZEOF
597
+ # @api private
598
+ SIZEOF = 32
599
+
600
+ # @api private
601
+ def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
602
+ super(cputype, cpusubtype, offset, size, align)
603
+ @reserved = reserved
604
+ end
605
+
606
+ # @return [String] the serialized fields of the fat arch
607
+ def serialize
608
+ [cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
609
+ end
610
+
611
+ # @return [Hash] a hash representation of this {FatArch64}
612
+ def to_h
613
+ {
614
+ "reserved" => reserved,
615
+ }.merge super
616
+ end
617
+ end
618
+
545
619
  # 32-bit Mach-O file header structure
546
620
  class MachHeader < MachOStructure
547
621
  # @return [Integer] the magic number
@@ -567,7 +641,7 @@ module MachO
567
641
 
568
642
  # @see MachOStructure::FORMAT
569
643
  # @api private
570
- FORMAT = "L=7".freeze
644
+ FORMAT = "L=7"
571
645
 
572
646
  # @see MachOStructure::SIZEOF
573
647
  # @api private
@@ -576,6 +650,7 @@ module MachO
576
650
  # @api private
577
651
  def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
578
652
  flags)
653
+ super()
579
654
  @magic = magic
580
655
  @cputype = cputype
581
656
  # For now we're not interested in additional capability bits also to be
@@ -593,7 +668,9 @@ module MachO
593
668
  # @return [Boolean] true if `flag` is present in the header's flag section
594
669
  def flag?(flag)
595
670
  flag = MH_FLAGS[flag]
671
+
596
672
  return false if flag.nil?
673
+
597
674
  flags & flag == flag
598
675
  end
599
676
 
@@ -688,7 +765,7 @@ module MachO
688
765
 
689
766
  # @see MachOStructure::FORMAT
690
767
  # @api private
691
- FORMAT = "L=8".freeze
768
+ FORMAT = "L=8"
692
769
 
693
770
  # @see MachOStructure::SIZEOF
694
771
  # @api private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing load commands in Mach-O binaries.
3
5
  module LoadCommands
@@ -60,6 +62,8 @@ module MachO
60
62
  0x30 => :LC_VERSION_MIN_WATCHOS,
61
63
  0x31 => :LC_NOTE,
62
64
  0x32 => :LC_BUILD_VERSION,
65
+ (0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
66
+ (0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
63
67
  }.freeze
64
68
 
65
69
  # association of symbol representations to load command constants
@@ -145,6 +149,8 @@ module MachO
145
149
  :LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
146
150
  :LC_NOTE => "NoteCommand",
147
151
  :LC_BUILD_VERSION => "BuildVersionCommand",
152
+ :LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
153
+ :LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
148
154
  }.freeze
149
155
 
150
156
  # association of segment name symbols to names
@@ -186,7 +192,7 @@ module MachO
186
192
 
187
193
  # @see MachOStructure::FORMAT
188
194
  # @api private
189
- FORMAT = "L=2".freeze
195
+ FORMAT = "L=2"
190
196
 
191
197
  # @see MachOStructure::SIZEOF
192
198
  # @api private
@@ -225,6 +231,7 @@ module MachO
225
231
  # @param cmdsize [Integer] the size of the load command in bytes
226
232
  # @api private
227
233
  def initialize(view, cmd, cmdsize)
234
+ super()
228
235
  @view = view
229
236
  @cmd = cmd
230
237
  @cmdsize = cmdsize
@@ -242,6 +249,7 @@ module MachO
242
249
  # @api private
243
250
  def serialize(context)
244
251
  raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable?
252
+
245
253
  format = Utils.specialize_format(FORMAT, context.endianness)
246
254
  [cmd, SIZEOF].pack(format)
247
255
  end
@@ -298,7 +306,9 @@ module MachO
298
306
  lc_end = view.offset + lc.cmdsize - 1
299
307
  raw_string = view.raw_data.slice(lc_str_abs..lc_end)
300
308
  @string, null_byte, _padding = raw_string.partition("\x00")
309
+
301
310
  raise LCStrMalformedError, lc if null_byte.empty?
311
+
302
312
  @string_offset = lc_str
303
313
  else
304
314
  @string = lc_str
@@ -362,7 +372,7 @@ module MachO
362
372
 
363
373
  # @see MachOStructure::FORMAT
364
374
  # @api private
365
- FORMAT = "L=2a16".freeze
375
+ FORMAT = "L=2a16"
366
376
 
367
377
  # @see MachOStructure::SIZEOF
368
378
  # @api private
@@ -376,7 +386,7 @@ module MachO
376
386
 
377
387
  # @return [String] a string representation of the UUID
378
388
  def uuid_string
379
- hexes = uuid.map { |e| "%02x" % e }
389
+ hexes = uuid.map { |elem| "%02<elem>x" % { :elem => elem } }
380
390
  segs = [
381
391
  hexes[0..3].join, hexes[4..5].join, hexes[6..7].join,
382
392
  hexes[8..9].join, hexes[10..15].join
@@ -426,7 +436,7 @@ module MachO
426
436
 
427
437
  # @see MachOStructure::FORMAT
428
438
  # @api private
429
- FORMAT = "L=2Z16L=4l=2L=2".freeze
439
+ FORMAT = "L=2Z16L=4l=2L=2"
430
440
 
431
441
  # @see MachOStructure::SIZEOF
432
442
  # @api private
@@ -473,7 +483,9 @@ module MachO
473
483
  # @return [Boolean] true if `flag` is present in the segment's flag field
474
484
  def flag?(flag)
475
485
  flag = SEGMENT_FLAGS[flag]
486
+
476
487
  return false if flag.nil?
488
+
477
489
  flags & flag == flag
478
490
  end
479
491
 
@@ -519,7 +531,7 @@ module MachO
519
531
  class SegmentCommand64 < SegmentCommand
520
532
  # @see MachOStructure::FORMAT
521
533
  # @api private
522
- FORMAT = "L=2Z16Q=4l=2L=2".freeze
534
+ FORMAT = "L=2Z16Q=4l=2L=2"
523
535
 
524
536
  # @see MachOStructure::SIZEOF
525
537
  # @api private
@@ -545,7 +557,7 @@ module MachO
545
557
 
546
558
  # @see MachOStructure::FORMAT
547
559
  # @api private
548
- FORMAT = "L=6".freeze
560
+ FORMAT = "L=6"
549
561
 
550
562
  # @see MachOStructure::SIZEOF
551
563
  # @api private
@@ -596,7 +608,7 @@ module MachO
596
608
 
597
609
  # @see MachOStructure::FORMAT
598
610
  # @api private
599
- FORMAT = "L=3".freeze
611
+ FORMAT = "L=3"
600
612
 
601
613
  # @see MachOStructure::SIZEOF
602
614
  # @api private
@@ -644,7 +656,7 @@ module MachO
644
656
 
645
657
  # @see MachOStructure::FORMAT
646
658
  # @api private
647
- FORMAT = "L=5".freeze
659
+ FORMAT = "L=5"
648
660
 
649
661
  # @see MachOStructure::SIZEOF
650
662
  # @api private
@@ -674,7 +686,7 @@ module MachO
674
686
  class ThreadCommand < LoadCommand
675
687
  # @see MachOStructure::FORMAT
676
688
  # @api private
677
- FORMAT = "L=2".freeze
689
+ FORMAT = "L=2"
678
690
 
679
691
  # @see MachOStructure::SIZEOF
680
692
  # @api private
@@ -712,7 +724,7 @@ module MachO
712
724
 
713
725
  # @see MachOStructure::FORMAT
714
726
  # @api private
715
- FORMAT = "L=10".freeze
727
+ FORMAT = "L=10"
716
728
 
717
729
  # @see MachOStructure::SIZEOF
718
730
  # @api private
@@ -753,7 +765,7 @@ module MachO
753
765
  class RoutinesCommand64 < RoutinesCommand
754
766
  # @see MachOStructure::FORMAT
755
767
  # @api private
756
- FORMAT = "L=2Q=8".freeze
768
+ FORMAT = "L=2Q=8"
757
769
 
758
770
  # @see MachOStructure::SIZEOF
759
771
  # @api private
@@ -768,7 +780,7 @@ module MachO
768
780
 
769
781
  # @see MachOStructure::FORMAT
770
782
  # @api private
771
- FORMAT = "L=3".freeze
783
+ FORMAT = "L=3"
772
784
 
773
785
  # @see MachOStructure::SIZEOF
774
786
  # @api private
@@ -796,7 +808,7 @@ module MachO
796
808
 
797
809
  # @see MachOStructure::FORMAT
798
810
  # @api private
799
- FORMAT = "L=3".freeze
811
+ FORMAT = "L=3"
800
812
 
801
813
  # @see MachOStructure::SIZEOF
802
814
  # @api private
@@ -824,7 +836,7 @@ module MachO
824
836
 
825
837
  # @see MachOStructure::FORMAT
826
838
  # @api private
827
- FORMAT = "L=3".freeze
839
+ FORMAT = "L=3"
828
840
 
829
841
  # @see MachOStructure::SIZEOF
830
842
  # @api private
@@ -852,7 +864,7 @@ module MachO
852
864
 
853
865
  # @see MachOStructure::FORMAT
854
866
  # @api private
855
- FORMAT = "L=3".freeze
867
+ FORMAT = "L=3"
856
868
 
857
869
  # @see MachOStructure::SIZEOF
858
870
  # @api private
@@ -889,7 +901,7 @@ module MachO
889
901
 
890
902
  # @see MachOStructure::FORMAT
891
903
  # @api private
892
- FORMAT = "L=6".freeze
904
+ FORMAT = "L=6"
893
905
 
894
906
  # @see MachOStructure::SIZEOF
895
907
  # @api private
@@ -974,7 +986,7 @@ module MachO
974
986
 
975
987
  # @see MachOStructure::FORMAT
976
988
  # @api private
977
- FORMAT = "L=20".freeze
989
+ FORMAT = "L=20"
978
990
 
979
991
  # @see MachOStructure::SIZEOF
980
992
  # @api private
@@ -1047,7 +1059,7 @@ module MachO
1047
1059
 
1048
1060
  # @see MachOStructure::FORMAT
1049
1061
  # @api private
1050
- FORMAT = "L=4".freeze
1062
+ FORMAT = "L=4"
1051
1063
 
1052
1064
  # @see MachOStructure::SIZEOF
1053
1065
  # @api private
@@ -1122,7 +1134,7 @@ module MachO
1122
1134
 
1123
1135
  # @see MachOStructure::FORMAT
1124
1136
  # @api private
1125
- FORMAT = "L=3".freeze
1137
+ FORMAT = "L=3"
1126
1138
 
1127
1139
  # @see MachOStructure::SIZEOF
1128
1140
  # @api private
@@ -1151,7 +1163,7 @@ module MachO
1151
1163
 
1152
1164
  # @see MachOStructure::FORMAT
1153
1165
  # @api private
1154
- FORMAT = "L=3".freeze
1166
+ FORMAT = "L=3"
1155
1167
 
1156
1168
  # @see MachOStructure::SIZEOF
1157
1169
  # @api private
@@ -1186,7 +1198,8 @@ module MachO
1186
1198
  # A load command representing the offsets and sizes of a blob of data in
1187
1199
  # the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE,
1188
1200
  # LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
1189
- # LC_DYLIB_CODE_SIGN_DRS, and LC_LINKER_OPTIMIZATION_HINT.
1201
+ # LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE,
1202
+ # or LC_DYLD_CHAINED_FIXUPS.
1190
1203
  class LinkeditDataCommand < LoadCommand
1191
1204
  # @return [Integer] offset to the data in the __LINKEDIT segment
1192
1205
  attr_reader :dataoff
@@ -1196,7 +1209,7 @@ module MachO
1196
1209
 
1197
1210
  # @see MachOStructure::FORMAT
1198
1211
  # @api private
1199
- FORMAT = "L=4".freeze
1212
+ FORMAT = "L=4"
1200
1213
 
1201
1214
  # @see MachOStructure::SIZEOF
1202
1215
  # @api private
@@ -1232,7 +1245,7 @@ module MachO
1232
1245
 
1233
1246
  # @see MachOStructure::FORMAT
1234
1247
  # @api private
1235
- FORMAT = "L=5".freeze
1248
+ FORMAT = "L=5"
1236
1249
 
1237
1250
  # @see MachOStructure::SIZEOF
1238
1251
  # @api private
@@ -1264,7 +1277,7 @@ module MachO
1264
1277
 
1265
1278
  # @see MachOStructure::FORMAT
1266
1279
  # @api private
1267
- FORMAT = "L=6".freeze
1280
+ FORMAT = "L=6"
1268
1281
 
1269
1282
  # @see MachOStructure::SIZEOF
1270
1283
  # @api private
@@ -1296,7 +1309,7 @@ module MachO
1296
1309
 
1297
1310
  # @see MachOStructure::FORMAT
1298
1311
  # @api private
1299
- FORMAT = "L=4".freeze
1312
+ FORMAT = "L=4"
1300
1313
 
1301
1314
  # @see MachOStructure::SIZEOF
1302
1315
  # @api private
@@ -1312,7 +1325,7 @@ module MachO
1312
1325
  # A string representation of the binary's minimum OS version.
1313
1326
  # @return [String] a string representing the minimum OS version.
1314
1327
  def version_string
1315
- binary = "%032b" % version
1328
+ binary = "%032<version>b" % { :version => version }
1316
1329
  segs = [
1317
1330
  binary[0..15], binary[16..23], binary[24..31]
1318
1331
  ].map { |s| s.to_i(2) }
@@ -1323,7 +1336,7 @@ module MachO
1323
1336
  # A string representation of the binary's SDK version.
1324
1337
  # @return [String] a string representing the SDK version.
1325
1338
  def sdk_string
1326
- binary = "%032b" % sdk
1339
+ binary = "%032<sdk>b" % { :sdk => sdk }
1327
1340
  segs = [
1328
1341
  binary[0..15], binary[16..23], binary[24..31]
1329
1342
  ].map { |s| s.to_i(2) }
@@ -1360,7 +1373,7 @@ module MachO
1360
1373
 
1361
1374
  # @see MachOStructure::FORMAT
1362
1375
  # @api private
1363
- FORMAT = "L=6".freeze
1376
+ FORMAT = "L=6"
1364
1377
 
1365
1378
  # @see MachOStructure::SIZEOF
1366
1379
  # @api private
@@ -1378,7 +1391,7 @@ module MachO
1378
1391
  # A string representation of the binary's minimum OS version.
1379
1392
  # @return [String] a string representing the minimum OS version.
1380
1393
  def minos_string
1381
- binary = "%032b" % minos
1394
+ binary = "%032<minos>b" % { :minos => minos }
1382
1395
  segs = [
1383
1396
  binary[0..15], binary[16..23], binary[24..31]
1384
1397
  ].map { |s| s.to_i(2) }
@@ -1389,7 +1402,7 @@ module MachO
1389
1402
  # A string representation of the binary's SDK version.
1390
1403
  # @return [String] a string representing the SDK version.
1391
1404
  def sdk_string
1392
- binary = "%032b" % sdk
1405
+ binary = "%032<sdk>b" % { :sdk => sdk }
1393
1406
  segs = [
1394
1407
  binary[0..15], binary[16..23], binary[24..31]
1395
1408
  ].map { |s| s.to_i(2) }
@@ -1489,7 +1502,7 @@ module MachO
1489
1502
 
1490
1503
  # @see MachOStructure::FORMAT
1491
1504
  # @api private
1492
- FORMAT = "L=12".freeze
1505
+ FORMAT = "L=12"
1493
1506
 
1494
1507
  # @see MachOStructure::SIZEOF
1495
1508
  # @api private
@@ -1537,7 +1550,7 @@ module MachO
1537
1550
 
1538
1551
  # @see MachOStructure::FORMAT
1539
1552
  # @api private
1540
- FORMAT = "L=3".freeze
1553
+ FORMAT = "L=3"
1541
1554
 
1542
1555
  # @see MachOStructure::SIZEOF
1543
1556
  # @api private
@@ -1567,7 +1580,7 @@ module MachO
1567
1580
 
1568
1581
  # @see MachOStructure::FORMAT
1569
1582
  # @api private
1570
- FORMAT = "L=2Q=2".freeze
1583
+ FORMAT = "L=2Q=2"
1571
1584
 
1572
1585
  # @see MachOStructure::SIZEOF
1573
1586
  # @api private
@@ -1597,7 +1610,7 @@ module MachO
1597
1610
 
1598
1611
  # @see MachOStructure::FORMAT
1599
1612
  # @api private
1600
- FORMAT = "L=2Q=1".freeze
1613
+ FORMAT = "L=2Q=1"
1601
1614
 
1602
1615
  # @see MachOStructure::SIZEOF
1603
1616
  # @api private
@@ -1612,7 +1625,7 @@ module MachO
1612
1625
  # A string representation of the sources used to build the binary.
1613
1626
  # @return [String] a string representation of the version
1614
1627
  def version_string
1615
- binary = "%064b" % version
1628
+ binary = "%064<version>b" % { :version => version }
1616
1629
  segs = [
1617
1630
  binary[0..23], binary[24..33], binary[34..43], binary[44..53],
1618
1631
  binary[54..63]
@@ -1641,7 +1654,7 @@ module MachO
1641
1654
 
1642
1655
  # @see MachOStructure::FORMAT
1643
1656
  # @api private
1644
- FORMAT = "L=4".freeze
1657
+ FORMAT = "L=4"
1645
1658
 
1646
1659
  # @see MachOStructure::SIZEOF
1647
1660
  # @api private
@@ -1669,7 +1682,7 @@ module MachO
1669
1682
  class IdentCommand < LoadCommand
1670
1683
  # @see MachOStructure::FORMAT
1671
1684
  # @api private
1672
- FORMAT = "L=2".freeze
1685
+ FORMAT = "L=2"
1673
1686
 
1674
1687
  # @see MachOStructure::SIZEOF
1675
1688
  # @api private
@@ -1687,7 +1700,7 @@ module MachO
1687
1700
 
1688
1701
  # @see MachOStructure::FORMAT
1689
1702
  # @api private
1690
- FORMAT = "L=4".freeze
1703
+ FORMAT = "L=4"
1691
1704
 
1692
1705
  # @see MachOStructure::SIZEOF
1693
1706
  # @api private
@@ -1722,7 +1735,7 @@ module MachO
1722
1735
 
1723
1736
  # @see MachOStructure::FORMAT
1724
1737
  # @api private
1725
- FORMAT = "L=5".freeze
1738
+ FORMAT = "L=5"
1726
1739
 
1727
1740
  # @see MachOStructure::SIZEOF
1728
1741
  # @api private
@@ -1759,7 +1772,7 @@ module MachO
1759
1772
 
1760
1773
  # @see MachOStructure::FORMAT
1761
1774
  # @api private
1762
- FORMAT = "L=2Z16Q=2".freeze
1775
+ FORMAT = "L=2Z16Q=2"
1763
1776
 
1764
1777
  # @see MachOStructure::SIZEOF
1765
1778
  # @api private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module MachO
@@ -9,10 +11,13 @@ module MachO
9
11
  class MachOFile
10
12
  extend Forwardable
11
13
 
12
- # @return [String] the filename loaded from, or nil if loaded from a binary
14
+ # @return [String, nil] the filename loaded from, or nil if loaded from a binary
13
15
  # string
14
16
  attr_accessor :filename
15
17
 
18
+ # @return [Hash] any parser options that the instance was created with
19
+ attr_reader :options
20
+
16
21
  # @return [Symbol] the endianness of the file, :big or :little
17
22
  attr_reader :endianness
18
23
 
@@ -27,30 +32,36 @@ module MachO
27
32
 
28
33
  # Creates a new instance from a binary string.
29
34
  # @param bin [String] a binary string containing raw Mach-O data
35
+ # @param opts [Hash] options to control the parser with
36
+ # @option opts [Boolean] :permissive whether to ignore unknown load commands
30
37
  # @return [MachOFile] a new MachOFile
31
- def self.new_from_bin(bin)
38
+ def self.new_from_bin(bin, **opts)
32
39
  instance = allocate
33
- instance.initialize_from_bin(bin)
40
+ instance.initialize_from_bin(bin, opts)
34
41
 
35
42
  instance
36
43
  end
37
44
 
38
45
  # Creates a new instance from data read from the given filename.
39
46
  # @param filename [String] the Mach-O file to load from
47
+ # @param opts [Hash] options to control the parser with
48
+ # @option opts [Boolean] :permissive whether to ignore unknown load commands
40
49
  # @raise [ArgumentError] if the given file does not exist
41
- def initialize(filename)
50
+ def initialize(filename, **opts)
42
51
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
43
52
 
44
53
  @filename = filename
54
+ @options = opts
45
55
  @raw_data = File.open(@filename, "rb", &:read)
46
56
  populate_fields
47
57
  end
48
58
 
49
- # Initializes a new MachOFile instance from a binary string.
59
+ # Initializes a new MachOFile instance from a binary string with the given options.
50
60
  # @see MachO::MachOFile.new_from_bin
51
61
  # @api private
52
- def initialize_from_bin(bin)
62
+ def initialize_from_bin(bin, opts)
53
63
  @filename = nil
64
+ @options = opts
54
65
  @raw_data = bin
55
66
  populate_fields
56
67
  end
@@ -146,16 +157,13 @@ module MachO
146
157
  def insert_command(offset, lc, options = {})
147
158
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
148
159
  cmd_raw = lc.serialize(context)
160
+ fileoff = offset + cmd_raw.bytesize
149
161
 
150
- if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
151
- raise OffsetInsertionError, offset
152
- end
162
+ raise OffsetInsertionError, offset if offset < header.class.bytesize || fileoff > low_fileoff
153
163
 
154
164
  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize
155
165
 
156
- if header.class.bytesize + new_sizeofcmds > low_fileoff
157
- raise HeaderPadError, @filename
158
- end
166
+ raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
159
167
 
160
168
  # update Mach-O header fields to account for inserted load command
161
169
  update_ncmds(ncmds + 1)
@@ -178,9 +186,8 @@ module MachO
178
186
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
179
187
  cmd_raw = new_lc.serialize(context)
180
188
  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
181
- if header.class.bytesize + new_sizeofcmds > low_fileoff
182
- raise HeaderPadError, @filename
183
- end
189
+
190
+ raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
184
191
 
185
192
  delete_command(old_lc)
186
193
  insert_command(old_lc.view.offset, new_lc)
@@ -431,6 +438,7 @@ module MachO
431
438
  # @note Overwrites all data in the file!
432
439
  def write!
433
440
  raise MachOError, "no initial file to write to" if @filename.nil?
441
+
434
442
  File.open(@filename, "wb") { |f| f.write(@raw_data) }
435
443
  end
436
444
 
@@ -470,7 +478,7 @@ module MachO
470
478
  # @raise [FatBinaryError] if the magic is for a Fat file
471
479
  # @api private
472
480
  def populate_and_check_magic
473
- magic = @raw_data[0..3].unpack("N").first
481
+ magic = @raw_data[0..3].unpack1("N")
474
482
 
475
483
  raise MagicError, magic unless Utils.magic?(magic)
476
484
  raise FatBinaryError if Utils.fat_magic?(magic)
@@ -510,19 +518,25 @@ module MachO
510
518
  # @raise [LoadCommandError] if an unknown load command is encountered
511
519
  # @api private
512
520
  def populate_load_commands
521
+ permissive = options.fetch(:permissive, false)
513
522
  offset = header.class.bytesize
514
523
  load_commands = []
515
524
 
516
525
  header.ncmds.times do
517
526
  fmt = Utils.specialize_format("L=", endianness)
518
- cmd = @raw_data.slice(offset, 4).unpack(fmt).first
527
+ cmd = @raw_data.slice(offset, 4).unpack1(fmt)
519
528
  cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
520
529
 
521
- raise LoadCommandError, cmd if cmd_sym.nil?
530
+ raise LoadCommandError, cmd unless cmd_sym || permissive
531
+
532
+ # If we're here, then either cmd_sym represents a valid load
533
+ # command *or* we're in permissive mode.
534
+ klass = if (klass_str = LoadCommands::LC_STRUCTURES[cmd_sym])
535
+ LoadCommands.const_get klass_str
536
+ else
537
+ LoadCommands::LoadCommand
538
+ end
522
539
 
523
- # why do I do this? i don't like declaring constants below
524
- # classes, and i need them to resolve...
525
- klass = LoadCommands.const_get LoadCommands::LC_STRUCTURES[cmd_sym]
526
540
  view = MachOView.new(@raw_data, endianness, offset)
527
541
  command = klass.new_from_bin(view)
528
542
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing sections in Mach-O binaries.
3
5
  module Sections
@@ -108,7 +110,7 @@ module MachO
108
110
  attr_reader :reserved2
109
111
 
110
112
  # @see MachOStructure::FORMAT
111
- FORMAT = "Z16Z16L=9".freeze
113
+ FORMAT = "Z16Z16L=9"
112
114
 
113
115
  # @see MachOStructure::SIZEOF
114
116
  SIZEOF = 68
@@ -116,6 +118,7 @@ module MachO
116
118
  # @api private
117
119
  def initialize(sectname, segname, addr, size, offset, align, reloff,
118
120
  nreloc, flags, reserved1, reserved2)
121
+ super()
119
122
  @sectname = sectname
120
123
  @segname = segname
121
124
  @addr = addr
@@ -150,7 +153,9 @@ module MachO
150
153
  # @return [Boolean] whether the flag is present in the section's {flags}
151
154
  def flag?(flag)
152
155
  flag = SECTION_FLAGS[flag]
156
+
153
157
  return false if flag.nil?
158
+
154
159
  flags & flag == flag
155
160
  end
156
161
 
@@ -178,7 +183,7 @@ module MachO
178
183
  attr_reader :reserved3
179
184
 
180
185
  # @see MachOStructure::FORMAT
181
- FORMAT = "Z16Z16Q=2L=8".freeze
186
+ FORMAT = "Z16Z16Q=2L=8"
182
187
 
183
188
  # @see MachOStructure::SIZEOF
184
189
  SIZEOF = 80
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A general purpose pseudo-structure.
3
5
  # @abstract
@@ -5,7 +7,7 @@ module MachO
5
7
  # The String#unpack format of the data structure.
6
8
  # @return [String] the unpacking format
7
9
  # @api private
8
- FORMAT = "".freeze
10
+ FORMAT = ""
9
11
 
10
12
  # The size of the data structure, in bytes.
11
13
  # @return [Integer] the size, in bytes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A collection of convenient methods for common operations on Mach-O and Fat
3
5
  # binaries.
@@ -89,8 +91,9 @@ module MachO
89
91
  # Merge multiple Mach-Os into one universal (Fat) binary.
90
92
  # @param filename [String] the fat binary to create
91
93
  # @param files [Array<String>] the files to merge
94
+ # @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
92
95
  # @return [void]
93
- def self.merge_machos(filename, *files)
96
+ def self.merge_machos(filename, *files, fat64: false)
94
97
  machos = files.map do |file|
95
98
  macho = MachO.open(file)
96
99
  case macho
@@ -101,7 +104,7 @@ module MachO
101
104
  end
102
105
  end.flatten
103
106
 
104
- fat_macho = MachO::FatFile.new_from_machos(*machos)
107
+ fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
105
108
  fat_macho.write(filename)
106
109
  end
107
110
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A collection of utility functions used throughout ruby-macho.
3
5
  module Utils
@@ -51,7 +53,7 @@ module MachO
51
53
  def self.pack_strings(fixed_offset, alignment, strings = {})
52
54
  offsets = {}
53
55
  next_offset = fixed_offset
54
- payload = ""
56
+ payload = +""
55
57
 
56
58
  strings.each do |key, string|
57
59
  offsets[key] = next_offset
@@ -61,7 +63,7 @@ module MachO
61
63
  end
62
64
 
63
65
  payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
64
- [payload, offsets]
66
+ [payload.freeze, offsets]
65
67
  end
66
68
 
67
69
  # Compares the given number to valid Mach-O magic numbers.
@@ -75,35 +77,49 @@ module MachO
75
77
  # @param num [Integer] the number being checked
76
78
  # @return [Boolean] whether `num` is a valid Fat magic number
77
79
  def self.fat_magic?(num)
80
+ [Headers::FAT_MAGIC, Headers::FAT_MAGIC_64].include? num
81
+ end
82
+
83
+ # Compares the given number to valid 32-bit Fat magic numbers.
84
+ # @param num [Integer] the number being checked
85
+ # @return [Boolean] whether `num` is a valid 32-bit fat magic number
86
+ def self.fat_magic32?(num)
78
87
  num == Headers::FAT_MAGIC
79
88
  end
80
89
 
90
+ # Compares the given number to valid 64-bit Fat magic numbers.
91
+ # @param num [Integer] the number being checked
92
+ # @return [Boolean] whether `num` is a valid 64-bit fat magic number
93
+ def self.fat_magic64?(num)
94
+ num == Headers::FAT_MAGIC_64
95
+ end
96
+
81
97
  # Compares the given number to valid 32-bit Mach-O magic numbers.
82
98
  # @param num [Integer] the number being checked
83
99
  # @return [Boolean] whether `num` is a valid 32-bit magic number
84
100
  def self.magic32?(num)
85
- num == Headers::MH_MAGIC || num == Headers::MH_CIGAM
101
+ [Headers::MH_MAGIC, Headers::MH_CIGAM].include? num
86
102
  end
87
103
 
88
104
  # Compares the given number to valid 64-bit Mach-O magic numbers.
89
105
  # @param num [Integer] the number being checked
90
106
  # @return [Boolean] whether `num` is a valid 64-bit magic number
91
107
  def self.magic64?(num)
92
- num == Headers::MH_MAGIC_64 || num == Headers::MH_CIGAM_64
108
+ [Headers::MH_MAGIC_64, Headers::MH_CIGAM_64].include? num
93
109
  end
94
110
 
95
111
  # Compares the given number to valid little-endian magic numbers.
96
112
  # @param num [Integer] the number being checked
97
113
  # @return [Boolean] whether `num` is a valid little-endian magic number
98
114
  def self.little_magic?(num)
99
- num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
115
+ [Headers::MH_CIGAM, Headers::MH_CIGAM_64].include? num
100
116
  end
101
117
 
102
118
  # Compares the given number to valid big-endian magic numbers.
103
119
  # @param num [Integer] the number being checked
104
120
  # @return [Boolean] whether `num` is a valid big-endian magic number
105
121
  def self.big_magic?(num)
106
- num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
122
+ [Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
107
123
  end
108
124
  end
109
125
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A representation of some unspecified Mach-O data.
3
5
  class MachOView
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-macho
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-03 00:00:00.000000000 Z
11
+ date: 2020-11-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A library for viewing and manipulating Mach-O files in Ruby.
14
14
  email: william@yossarian.net
@@ -34,7 +34,7 @@ homepage: https://github.com/Homebrew/ruby-macho
34
34
  licenses:
35
35
  - MIT
36
36
  metadata: {}
37
- post_install_message:
37
+ post_install_message:
38
38
  rdoc_options: []
39
39
  require_paths:
40
40
  - lib
@@ -42,16 +42,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '2.1'
45
+ version: '2.5'
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
50
  version: '0'
51
51
  requirements: []
52
- rubyforge_project:
53
- rubygems_version: 2.7.6
54
- signing_key:
52
+ rubygems_version: 3.1.2
53
+ signing_key:
55
54
  specification_version: 4
56
55
  summary: ruby-macho - Mach-O file analyzer.
57
56
  test_files: []