ruby-macho 2.0.0 → 2.5.0

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.
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: []